blob: 29f06debdd0ae6208708026c9a2d49430daade0e [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 "integration-test.h"
#include <lib/async/cpp/executor.h>
#include <lib/async/cpp/wait.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/unsafe.h>
#include <lib/fpromise/bridge.h>
#include <lib/zbi-format/zbi.h>
#include <zircon/status.h>
#include <sstream>
namespace libdriver_integration_test {
async::Loop IntegrationTest::loop_{&kAsyncLoopConfigNoAttachToCurrentThread};
IntegrationTest::IsolatedDevmgr IntegrationTest::devmgr_;
void IntegrationTest::SetUpTestSuite() { DoSetup(); }
void IntegrationTest::DoSetup() {
// Set up the isolated devmgr instance for this test suite. Note that we
// only do this once for the whole suite, because it is currently an
// expensive process. Ideally we'd do this between every test.
auto args = IsolatedDevmgr::DefaultArgs();
zx::result devmgr = IsolatedDevmgr::Create(std::move(args), loop_.dispatcher());
ASSERT_TRUE(devmgr.is_ok()) << devmgr.status_string();
IntegrationTest::devmgr_ = std::move(devmgr.value());
}
void IntegrationTest::TearDownTestSuite() { IntegrationTest::devmgr_.reset(); }
IntegrationTest::IntegrationTest() = default;
void IntegrationTest::SetUp() {
// We do this in SetUp() rather than the ctor, since gtest cannot assert in
// ctors.
zx::channel channel;
ASSERT_EQ(
fdio_fd_clone(IntegrationTest::devmgr_.devfs_root().get(), channel.reset_and_get_address()),
ZX_OK);
ASSERT_EQ(devfs_.Bind(std::move(channel), IntegrationTest::loop_.dispatcher()), ZX_OK);
}
IntegrationTest::~IntegrationTest() {
IntegrationTest::loop_.Quit();
IntegrationTest::loop_.ResetQuit();
}
void IntegrationTest::RunPromise(Promise<void> promise) {
async::Executor executor(IntegrationTest::loop_.dispatcher());
auto new_promise = promise.then([&](Promise<void>::result_type& result) {
if (result.is_error()) {
ADD_FAILURE() << result.error();
}
IntegrationTest::loop_.Quit();
return result;
});
executor.schedule_task(std::move(new_promise));
zx_status_t status = IntegrationTest::loop_.Run();
ASSERT_EQ(status, ZX_ERR_CANCELED);
}
IntegrationTest::Promise<void> IntegrationTest::CreateFirstChild(
std::unique_ptr<RootMockDevice>* root_mock_device, std::unique_ptr<MockDevice>* child_device) {
return ExpectBind(root_mock_device, [root_mock_device, child_device](HookInvocation record,
Completer<void> completer) {
ActionList actions;
actions.AppendAddMockDevice(IntegrationTest::loop_.dispatcher(), (*root_mock_device)->path(),
"first_child", std::vector<zx_device_prop_t>{}, ZX_OK,
std::move(completer), child_device);
actions.AppendReturnStatus(ZX_OK);
return actions;
});
}
IntegrationTest::Promise<void> IntegrationTest::ExpectUnbindThenRelease(
const std::unique_ptr<MockDevice>& device) {
fpromise::bridge<void, Error> bridge;
auto unbind = ExpectUnbind(device, [unbind_reply_completer = std::move(bridge.completer)](
HookInvocation record, Completer<void> completer) mutable {
completer.complete_ok();
ActionList actions;
actions.AppendUnbindReply(std::move(unbind_reply_completer));
return actions;
});
auto reply_done =
bridge.consumer.promise_or(::fpromise::error("unbind_reply_completer abandoned"));
return unbind.and_then(JoinPromises(std::move(reply_done), ExpectRelease(device)));
}
IntegrationTest::Promise<void> IntegrationTest::ExpectBind(
std::unique_ptr<RootMockDevice>* root_mock_device, BindOnce::Callback actions_callback) {
fpromise::bridge<void, Error> bridge;
auto bind_hook =
std::make_unique<BindOnce>(std::move(bridge.completer), std::move(actions_callback));
zx_status_t status = RootMockDevice::Create(devmgr_, IntegrationTest::loop_.dispatcher(),
std::move(bind_hook), root_mock_device);
PROMISE_ASSERT(ASSERT_EQ(status, ZX_OK));
return bridge.consumer.promise_or(::fpromise::error("bind abandoned"));
}
IntegrationTest::Promise<void> IntegrationTest::ExpectUnbind(
const std::unique_ptr<MockDevice>& device, UnbindOnce::Callback actions_callback) {
fpromise::bridge<void, Error> bridge;
auto unbind_hook =
std::make_unique<UnbindOnce>(std::move(bridge.completer), std::move(actions_callback));
// Wrap the body in a promise, since we want to defer the evaluation of
// device->set_hooks.
return fpromise::make_promise([consumer = std::move(bridge.consumer), &device,
unbind_hook = std::move(unbind_hook)]() mutable {
device->set_hooks(std::move(unbind_hook));
return consumer.promise_or(::fpromise::error("unbind abandoned"));
});
}
IntegrationTest::Promise<void> IntegrationTest::ExpectRelease(
const std::unique_ptr<MockDevice>& device) {
// Wrap the body in a promise, since we want to defer the evaluation of
// device->set_hooks.
return fpromise::make_promise([&device]() {
fpromise::bridge<void, Error> bridge;
ReleaseOnce::Callback func = [](HookInvocation record, Completer<void> completer) {
completer.complete_ok();
};
auto release_hook = std::make_unique<ReleaseOnce>(std::move(bridge.completer), std::move(func));
device->set_hooks(std::move(release_hook));
return bridge.consumer.promise_or(::fpromise::error("release abandoned"));
});
}
IntegrationTest::Promise<void> IntegrationTest::DoOpen(
const std::string& path, fidl::InterfacePtr<fuchsia::io::Node>* client) {
fidl::InterfaceRequest<fuchsia::io::Node> server(
client->NewRequest(IntegrationTest::IntegrationTest::loop_.dispatcher()));
PROMISE_ASSERT(ASSERT_TRUE(server.is_valid()));
PROMISE_ASSERT(ASSERT_EQ(client->events().OnOpen, nullptr));
fpromise::bridge<void, Error> bridge;
client->events().OnOpen = [client, completer = std::move(bridge.completer)](
zx_status_t status,
std::unique_ptr<fuchsia::io::NodeInfoDeprecated> info) mutable {
if (status != ZX_OK) {
std::string error("failed to open node: ");
error.append(zx_status_get_string(status));
completer.complete_error(std::move(error));
client->events().OnOpen = nullptr;
return;
}
completer.complete_ok();
client->events().OnOpen = nullptr;
};
devfs_->Open(fuchsia::io::OpenFlags::DESCRIBE, {}, path, std::move(server));
return bridge.consumer.promise_or(::fpromise::error("devfs open abandoned"));
}
namespace {
class AsyncWatcher {
public:
AsyncWatcher(std::string path, fidl::InterfaceHandle<fuchsia::io::DirectoryWatcher> watcher)
: path_(std::move(path)),
watcher_(std::move(watcher)),
wait_(this, watcher_.channel().get(), ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED, 0) {}
void WatcherChanged(async_dispatcher_t* dispatcher, async::WaitBase* wait, zx_status_t status,
const zx_packet_signal_t* signal) {
if (status != ZX_OK) {
std::ostringstream os;
os << "watcher callback error: " << zx_status_get_string(status);
cb_(fit::error(os.str()));
return;
}
if (signal->observed & ZX_CHANNEL_READABLE) {
char buf[fuchsia::io::MAX_BUF + 1];
uint32_t bytes_read;
zx_status_t status =
watcher_.channel().read(0, buf, nullptr, sizeof(buf) - 1, 0, &bytes_read, nullptr);
if (status != ZX_OK) {
std::ostringstream os;
os << "watcher read error: " << zx_status_get_string(status);
cb_(fit::error(os.str()));
return;
}
size_t bytes_processed = 0;
while (bytes_read - bytes_processed > 2) {
[[maybe_unused]] uint8_t event = buf[bytes_processed++];
uint8_t name_length = buf[bytes_processed++];
if (size_t remaining = bytes_read - bytes_processed; remaining < name_length) {
std::ostringstream os;
os << "watcher read error: name length (" << name_length << ") > remaining (" << remaining
<< ")";
cb_(fit::error(os.str()));
return;
}
const std::string_view filename{&buf[bytes_processed], name_length};
if (filename == path_) {
cb_(fit::ok());
return;
}
bytes_processed += name_length;
}
wait->Begin(dispatcher);
} else if (signal->observed & ZX_CHANNEL_PEER_CLOSED) {
std::ostringstream os;
os << "watcher read error: " << zx_status_get_string(status);
cb_(fit::error(os.str()));
return;
}
}
void Begin(async_dispatcher_t* dispatcher,
fit::callback<void(fit::result<IntegrationTest::Error>)> cb) {
cb_ = std::move(cb);
if (const zx_status_t status = wait_.Begin(dispatcher); status != ZX_OK) {
std::ostringstream os;
os << "watcher error: " << zx_status_get_string(status);
cb_(fit::error(os.str()));
}
}
const std::string& path() const { return path_; }
private:
const std::string path_;
const fidl::InterfaceHandle<fuchsia::io::DirectoryWatcher> watcher_;
fit::callback<void(fit::result<IntegrationTest::Error>)> cb_;
async::WaitMethod<AsyncWatcher, &AsyncWatcher::WatcherChanged> wait_;
};
void WaitForPath(const fidl::InterfacePtr<fuchsia::io::Directory>& dir,
async_dispatcher_t* dispatcher, std::string path,
fit::callback<void(fit::result<IntegrationTest::Error>)> cb) {
fidl::InterfaceHandle<fuchsia::io::DirectoryWatcher> watcher;
fidl::InterfaceRequest<fuchsia::io::DirectoryWatcher> request = watcher.NewRequest();
std::optional<std::string> next_path;
const size_t slash = path.find('/');
if (slash != std::string::npos) {
next_path = path.substr(slash + 1);
path = path.substr(0, slash);
}
dir->Watch(
fuchsia::io::WatchMask::ADDED | fuchsia::io::WatchMask::EXISTING, 0, std::move(request),
[dispatcher, path = std::move(path), watcher = std::move(watcher), &dir,
next_path = std::move(next_path), cb = std::move(cb)](zx_status_t status) mutable {
if (status != ZX_OK) {
std::ostringstream os;
os << "watcher error: " << zx_status_get_string(status);
cb(fit::error(os.str()));
return;
}
std::unique_ptr async_watcher =
std::make_unique<AsyncWatcher>(std::move(path), std::move(watcher));
async_watcher->Begin(
dispatcher, [dispatcher, next_path = std::move(next_path), cb = std::move(cb),
watcher = std::move(async_watcher),
&dir](fit::result<IntegrationTest::Error> result) mutable {
if (result.is_error()) {
cb(result.take_error());
return;
}
if (!next_path.has_value()) {
cb(fit::ok());
return;
}
fidl::InterfaceHandle<fuchsia::io::Node> node;
fidl::InterfaceRequest<fuchsia::io::Node> request = node.NewRequest();
fidl::InterfacePtr<fuchsia::io::Directory> subdir;
if (const zx_status_t status = subdir.Bind(node.TakeChannel(), dispatcher);
status != ZX_OK) {
std::ostringstream os;
os << "bind: " << zx_status_get_string(status);
cb(fit::error(os.str()));
return;
}
std::string dirname = watcher->path();
auto& events = subdir.events();
events.OnOpen = [dispatcher, subdir = std::move(subdir),
path = std::move(next_path.value()), cb = std::move(cb), dirname](
zx_status_t status,
std::unique_ptr<fuchsia::io::NodeInfoDeprecated> info) mutable {
if (status != ZX_OK) {
std::ostringstream os;
os << "Open(" << dirname << ").OnOpen: " << zx_status_get_string(status);
cb(fit::error(os.str()));
return;
}
std::unique_ptr pinned_subdir =
std::make_unique<fidl::InterfacePtr<fuchsia::io::Directory>>(std::move(subdir));
WaitForPath(*pinned_subdir, dispatcher, std::move(path),
[pinned_subdir = std::move(pinned_subdir), cb = std::move(cb)](
fit::result<IntegrationTest::Error> result) mutable {
cb(std::move(result));
});
};
dir->Open(fuchsia::io::OpenFlags::DIRECTORY | fuchsia::io::OpenFlags::DESCRIBE, {},
std::move(dirname), std::move(request));
});
});
}
} // namespace
IntegrationTest::Promise<void> IntegrationTest::DoWaitForPath(const std::string& path) {
fpromise::bridge<void, Error> bridge;
WaitForPath(devfs_, loop_.dispatcher(), path,
[completer = std::move(bridge.completer)](
fit::result<IntegrationTest::Error> result) mutable {
if (result.is_error()) {
completer.complete_error(result.error_value());
} else {
completer.complete_ok();
}
});
return bridge.consumer.promise_or(::fpromise::error("WaitForPath abandoned"));
}
} // namespace libdriver_integration_test