blob: 1dd38467163b77e345d8e37df0b3c996c3cfe00c [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 "src/developer/cmd/console.h"
#include <fcntl.h>
#include <fuchsia/hardware/pty/c/fidl.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/io.h>
#include <lib/fit/function.h>
#include <lib/zx/channel.h>
#include <lib/zx/socket.h>
#include <poll.h>
#include <unistd.h>
#include <zircon/status.h>
#include <utility>
#include <fbl/unique_fd.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/lib/testing/loop_fixture/real_loop_fixture.h"
namespace {
using Console = gtest::RealLoopFixture;
class CallbackClient : public cmd::Console::Client {
public:
~CallbackClient() override = default;
zx_status_t OnConsoleCommand(cmd::Command command) override {
return console_command_callback(std::move(command));
}
void OnConsoleInterrupt() override { console_interrupt_callback(); }
void OnConsoleError(zx_status_t status) override { console_error_callback(status); }
void OnConsoleAutocomplete(cmd::Autocomplete* autocomplete) override {
return console_autocomplete_callback(autocomplete);
}
fit::function<zx_status_t(cmd::Command)> console_command_callback;
fit::closure console_interrupt_callback;
fit::function<void(zx_status_t status)> console_error_callback;
fit::function<void(cmd::Autocomplete* autocomplete)> console_autocomplete_callback;
};
TEST_F(Console, Control) {
zx::socket h0, h1;
EXPECT_EQ(ZX_OK, zx::socket::create(0, &h0, &h1));
fbl::unique_fd input_fd;
EXPECT_EQ(ZX_OK, fdio_fd_create(h1.release(), input_fd.reset_and_get_address()));
auto unexpected_command_callback = [](cmd::Command command) {
EXPECT_TRUE(false) << "OnConsoleCommand called unexpectedly";
return ZX_ERR_BAD_STATE;
};
auto unexpected_interrupt_callback = []() {
EXPECT_TRUE(false) << "OnConsoleInterrupt called unexpectedly";
};
auto unexpected_error_callback = [](zx_status_t status) {
EXPECT_TRUE(false) << "OnConsoleError called unexpectedly; status = " << status << " ("
<< zx_status_get_string(status) << ")";
};
auto unexpected_autocomplete_callback = [](cmd::Autocomplete* autocomplete) {
EXPECT_TRUE(false) << "OnConsoleAutocomplete called unexpectedly";
};
CallbackClient client;
client.console_command_callback = unexpected_command_callback;
client.console_interrupt_callback = unexpected_interrupt_callback;
client.console_error_callback = unexpected_error_callback;
client.console_autocomplete_callback = unexpected_autocomplete_callback;
cmd::Console console(&client, dispatcher(), input_fd.get());
console.Init("test> ");
console.GetNextCommand();
const char* input = "command1 arg0 arg1\ncommand2 xxx yyy zzz\ncommand3";
size_t actual = 0;
EXPECT_EQ(ZX_OK, h0.write(0, input, strlen(input), &actual));
EXPECT_EQ(strlen(input), actual);
int command_count = 0;
client.console_command_callback = [&command_count](cmd::Command command) {
command_count++;
if (command_count == 1) {
EXPECT_EQ(3u, command.args().size());
EXPECT_EQ("command1", command.args()[0]);
return ZX_ERR_NEXT;
} else {
EXPECT_EQ(4u, command.args().size());
EXPECT_EQ("command2", command.args()[0]);
return ZX_ERR_ASYNC;
}
};
RunLoopUntilIdle();
EXPECT_EQ(2, command_count);
int error_count = 0;
client.console_command_callback = unexpected_command_callback;
client.console_error_callback = [&error_count](zx_status_t status) {
error_count++;
EXPECT_EQ(ZX_ERR_PEER_CLOSED, status);
};
h0.reset();
console.GetNextCommand();
RunLoopUntilIdle();
EXPECT_EQ(1, error_count);
}
static zx_status_t open_client(int fd, uint32_t client_id, int* out_fd) {
if (!out_fd) {
return ZX_ERR_INVALID_ARGS;
}
fdio_t* io = fdio_unsafe_fd_to_io(fd);
if (!io) {
return ZX_ERR_INTERNAL;
}
zx::channel device_channel, client_channel;
zx_status_t status = zx::channel::create(0, &device_channel, &client_channel);
if (status != ZX_OK) {
return status;
}
zx_status_t fidl_status = fuchsia_hardware_pty_DeviceOpenClient(
fdio_unsafe_borrow_channel(io), client_id, device_channel.release(), &status);
if (status != ZX_OK) {
return status;
}
fdio_unsafe_release(io);
if (fidl_status != ZX_OK) {
return fidl_status;
}
if (status != ZX_OK) {
return status;
}
status = fdio_fd_create(client_channel.release(), out_fd);
if (status != ZX_OK) {
return status;
}
return fcntl(*out_fd, F_SETFL, O_NONBLOCK);
}
TEST_F(Console, Interrupt) {
fbl::unique_fd ps;
{
zx::channel local, remote;
ASSERT_EQ(ZX_OK, zx::channel::create(0, &local, &remote));
ASSERT_EQ(ZX_OK, fdio_service_connect("/svc/fuchsia.hardware.pty.Device", remote.release()));
int fd;
ASSERT_EQ(ZX_OK, fdio_fd_create(local.release(), &fd));
ps.reset(fd);
ASSERT_TRUE(ps.is_valid());
int flags = fcntl(ps.get(), F_GETFL);
ASSERT_LE(0, flags);
ASSERT_EQ(0, fcntl(ps.get(), F_SETFL, flags | O_NONBLOCK));
}
int pc_fd;
ASSERT_EQ(ZX_OK, open_client(ps.get(), 0, &pc_fd));
ASSERT_LE(0, pc_fd);
fbl::unique_fd pc(pc_fd);
ASSERT_EQ(true, bool(pc));
auto unexpected_command_callback = [](cmd::Command command) {
EXPECT_TRUE(false) << "OnConsoleCommand called unexpectedly";
return ZX_ERR_BAD_STATE;
};
auto unexpected_interrupt_callback = []() {
EXPECT_TRUE(false) << "OnConsoleInterrupt called unexpectedly";
};
auto unexpected_error_callback = [](zx_status_t status) {
EXPECT_TRUE(false) << "OnConsoleError called unexpectedly; status = " << status << " ("
<< zx_status_get_string(status) << ")";
};
auto unexpected_autocomplete_callback = [](cmd::Autocomplete* autocomplete) {
EXPECT_TRUE(false) << "OnConsoleAutocomplete called unexpectedly";
};
CallbackClient client;
client.console_command_callback = unexpected_command_callback;
client.console_interrupt_callback = unexpected_interrupt_callback;
client.console_error_callback = unexpected_error_callback;
client.console_autocomplete_callback = unexpected_autocomplete_callback;
cmd::Console console(&client, dispatcher(), pc.get());
console.Init("test> ");
console.GetNextCommand();
ASSERT_EQ(write(ps.get(), "xyzzy", 5), 5);
ASSERT_EQ(fdio_wait_fd(pc.get(), POLLIN, nullptr, ZX_TIME_INFINITE), ZX_OK);
RunLoopUntilIdle();
ASSERT_EQ(write(ps.get(), "\x3", 1), 1);
ASSERT_EQ(fdio_wait_fd(pc.get(), POLLPRI, nullptr, ZX_TIME_INFINITE), ZX_OK);
RunLoopUntilIdle();
int command_count = 0;
client.console_command_callback = [&command_count](cmd::Command command) {
command_count++;
return ZX_ERR_ASYNC;
};
ASSERT_EQ(write(ps.get(), "abc\n", 4), 4);
ASSERT_EQ(fdio_wait_fd(pc.get(), POLLIN, nullptr, ZX_TIME_INFINITE), ZX_OK);
RunLoopUntilIdle();
ASSERT_EQ(1, command_count);
int interrupt_count = 0;
client.console_interrupt_callback = [&interrupt_count]() { interrupt_count++; };
ASSERT_EQ(write(ps.get(), "\x3", 1), 1);
ASSERT_EQ(fdio_wait_fd(pc.get(), POLLPRI, nullptr, ZX_TIME_INFINITE), ZX_OK);
RunLoopUntilIdle();
ASSERT_EQ(1, interrupt_count);
}
} // namespace