blob: 2f7487f3cf7128a9c8a4ffd4529a3ae68a23b711 [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 <fcntl.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/fdio.h>
#include <lib/fdio/unsafe.h>
#include <lib/fit/bridge.h>
#include <zircon/boot/image.h>
#include <zircon/status.h>
namespace libdriver_integration_test {
async::Loop IntegrationTest::loop_{&kAsyncLoopConfigNoAttachToCurrentThread};
IntegrationTest::IsolatedDevmgr IntegrationTest::devmgr_;
void IntegrationTest::SetUpTestCase() { DoSetup(false /* should_create_composite */); }
void IntegrationTest::DoSetup(bool should_create_composite) {
// 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();
args.stdio = fbl::unique_fd(open("/dev/null", O_RDWR));
args.load_drivers.push_back("/boot/driver/fragment.so");
args.load_drivers.push_back("/boot/driver/fragment.proxy.so");
args.driver_search_paths.push_back("/boot/driver");
// Rig up a get_boot_item that will send configuration information over to
// the sysdev driver.
args.get_boot_item = [should_create_composite](uint32_t type, uint32_t extra, zx::vmo* vmo,
uint32_t* length) {
vmo->reset();
*length = 0;
if (type != ZBI_TYPE_DRV_BOARD_PRIVATE || extra != 0) {
return ZX_OK;
}
zx::vmo data;
zx_status_t status = zx::vmo::create(1, 0, &data);
if (status != ZX_OK) {
return status;
}
status = data.write(reinterpret_cast<const void*>(&should_create_composite), 0,
sizeof(should_create_composite));
if (status != ZX_OK) {
return status;
}
*length = sizeof(should_create_composite);
*vmo = std::move(data);
return ZX_OK;
};
zx_status_t status =
IsolatedDevmgr::Create(std::move(args), loop_.dispatcher(), &IntegrationTest::devmgr_);
ASSERT_EQ(status, ZX_OK) << "failed to create IsolatedDevmgr";
IntegrationTest::devmgr_.SetExceptionCallback(DevmgrException);
}
void IntegrationTest::TearDownTestCase() { IntegrationTest::devmgr_.reset(); }
IntegrationTest::IntegrationTest() {}
void IntegrationTest::SetUp() {
// We do this in SetUp() rather than the ctor, since gtest cannot assert in
// ctors.
fdio_t* io = fdio_unsafe_fd_to_io(IntegrationTest::devmgr_.devfs_root().get());
zx::channel chan(fdio_service_clone(fdio_unsafe_borrow_channel(io)));
zx_status_t status = devfs_.Bind(std::move(chan), IntegrationTest::loop_.dispatcher());
fdio_unsafe_release(io);
ASSERT_EQ(status, ZX_OK) << "failed to connect to devfs";
}
IntegrationTest::~IntegrationTest() {
IntegrationTest::loop_.Quit();
IntegrationTest::loop_.ResetQuit();
}
void IntegrationTest::DevmgrException() {
// Log an error in the currently running test
ADD_FAILURE() << "Crash inside devmgr job";
IntegrationTest::loop_.Quit();
}
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) {
fit::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(::fit::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) {
fit::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(::fit::error("bind abandoned"));
}
IntegrationTest::Promise<void> IntegrationTest::ExpectUnbind(
const std::unique_ptr<MockDevice>& device, UnbindOnce::Callback actions_callback) {
fit::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 fit::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(::fit::error("unbind abandoned"));
});
}
IntegrationTest::Promise<void> IntegrationTest::ExpectOpen(
const std::unique_ptr<MockDevice>& device, OpenOnce::Callback actions_callback) {
fit::bridge<void, Error> bridge;
auto open_hook =
std::make_unique<OpenOnce>(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 fit::make_promise(
[consumer = std::move(bridge.consumer), &device, open_hook = std::move(open_hook)]() mutable {
device->set_hooks(std::move(open_hook));
return consumer.promise_or(::fit::error("open abandoned"));
});
}
IntegrationTest::Promise<void> IntegrationTest::ExpectClose(
const std::unique_ptr<MockDevice>& device, CloseOnce::Callback actions_callback) {
fit::bridge<void, Error> bridge;
auto close_hook =
std::make_unique<CloseOnce>(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 fit::make_promise([consumer = std::move(bridge.consumer), &device,
close_hook = std::move(close_hook)]() mutable {
device->set_hooks(std::move(close_hook));
return consumer.promise_or(::fit::error("close 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 fit::make_promise([&device]() {
fit::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(::fit::error("release abandoned"));
});
}
IntegrationTest::Promise<void> IntegrationTest::DoOpen(
const std::string& path, fidl::InterfacePtr<fuchsia::io::Node>* client, uint32_t flags) {
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));
fit::bridge<void, Error> bridge;
client->events().OnOpen = [client, completer = std::move(bridge.completer)](
zx_status_t status,
std::unique_ptr<fuchsia::io::NodeInfo> 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(flags | fuchsia::io::OPEN_FLAG_DESCRIBE, 0, path, std::move(server));
return bridge.consumer.promise_or(::fit::error("devfs open abandoned"));
}
namespace {
class AsyncWatcher {
public:
AsyncWatcher(std::string path, zx::channel watcher, fidl::InterfacePtr<fuchsia::io::Node> node)
: path_(std::move(path)),
watcher_(std::move(watcher)),
connections_{std::move(node), {}},
wait_(watcher_.get(), ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED, 0,
fit::bind_member(this, &AsyncWatcher::WatcherChanged)) {}
void WatcherChanged(async_dispatcher_t* dispatcher, async::Wait* wait, zx_status_t status,
const zx_packet_signal_t* signal);
zx_status_t Begin(async_dispatcher_t* dispatcher,
fit::completer<void, IntegrationTest::Error> completer) {
completer_ = std::move(completer);
return wait_.Begin(dispatcher);
}
// Directory handle to keep alive for the lifetime of the AsyncWatcher, if
// necessary.
struct Connections {
fidl::InterfacePtr<fuchsia::io::Node> node;
fidl::InterfacePtr<fuchsia::io::Directory> directory;
};
Connections& connections() { return connections_; }
private:
std::string path_;
zx::channel watcher_;
Connections connections_;
async::Wait wait_;
fit::completer<void, IntegrationTest::Error> completer_;
};
void AsyncWatcher::WatcherChanged(async_dispatcher_t* dispatcher, async::Wait* wait,
zx_status_t status, const zx_packet_signal_t* signal) {
auto error = [&](const char* msg) {
completer_.complete_error(msg);
delete this;
};
if (status != ZX_OK) {
return error("watcher error");
}
if (signal->observed & ZX_CHANNEL_READABLE) {
char buf[fuchsia::io::MAX_BUF + 1];
uint32_t bytes_read;
status = watcher_.read(0, buf, nullptr, sizeof(buf) - 1, 0, &bytes_read, nullptr);
if (status != ZX_OK) {
return error("watcher read error");
}
size_t bytes_processed = 0;
while (bytes_processed + 2 < bytes_read) {
char* msg = &buf[bytes_processed];
uint8_t name_length = msg[1];
if (bytes_processed + 2 + name_length > bytes_read) {
return error("watcher read error");
}
char* filename = &msg[2];
uint8_t tmp = filename[name_length];
filename[name_length] = 0;
if (!strcmp(path_.c_str(), filename)) {
completer_.complete_ok();
delete this;
return;
}
filename[name_length] = tmp;
bytes_processed += 2 + name_length;
}
wait->Begin(dispatcher);
} else if (signal->observed & ZX_CHANNEL_PEER_CLOSED) {
return error("watcher closed");
}
}
void WaitForPath(const fidl::InterfacePtr<fuchsia::io::Directory>& dir,
async_dispatcher_t* dispatcher, std::string path,
fit::completer<void, IntegrationTest::Error> completer) {
zx::channel watcher, remote;
ASSERT_EQ(zx::channel::create(0, &watcher, &remote), ZX_OK);
fidl::InterfacePtr<fuchsia::io::Node> last_dir;
std::string filename;
size_t last_slash = path.find_last_of('/');
if (last_slash != std::string::npos) {
std::string prefix(path, 0, last_slash);
dir->Open(fuchsia::io::OPEN_FLAG_DIRECTORY | fuchsia::io::OPEN_FLAG_DESCRIBE |
fuchsia::io::OPEN_RIGHT_READABLE | fuchsia::io::OPEN_RIGHT_WRITABLE,
0, prefix, last_dir.NewRequest(dispatcher));
filename = path.substr(last_slash + 1);
} else {
dir->Clone(fuchsia::io::CLONE_FLAG_SAME_RIGHTS | fuchsia::io::OPEN_FLAG_DESCRIBE,
last_dir.NewRequest(dispatcher));
filename = path;
}
auto async_watcher =
std::make_unique<AsyncWatcher>(std::move(filename), std::move(watcher), std::move(last_dir));
auto& events = async_watcher->connections().node.events();
events.OnOpen = [dispatcher, async_watcher = std::move(async_watcher),
completer = std::move(completer), remote = std::move(remote)](
zx_status_t status, std::unique_ptr<fuchsia::io::NodeInfo> info) mutable {
if (status != ZX_OK) {
completer.complete_error("Failed to open directory");
return;
}
auto& dir = async_watcher->connections().directory;
dir.Bind(async_watcher->connections().node.Unbind().TakeChannel(), dispatcher);
dir->Watch(fuchsia::io::WATCH_MASK_ADDED | fuchsia::io::WATCH_MASK_EXISTING, 0,
std::move(remote),
[dispatcher, async_watcher = std::move(async_watcher),
completer = std::move(completer)](zx_status_t status) mutable {
if (status == ZX_OK) {
status = async_watcher->Begin(dispatcher, std::move(completer));
if (status == ZX_OK) {
// The async_watcher will clean this up
__UNUSED auto ptr = async_watcher.release();
return;
}
}
completer.complete_error("watcher failed");
});
};
}
} // namespace
IntegrationTest::Promise<void> IntegrationTest::DoWaitForPath(const std::string& path) {
fit::bridge<void, Error> bridge;
WaitForPath(devfs_, IntegrationTest::loop_.dispatcher(), path, std::move(bridge.completer));
return bridge.consumer.promise_or(::fit::error("WaitForPath abandoned"));
}
} // namespace libdriver_integration_test