blob: 0f5cacc274bd85db54499125b56069219737bef6 [file]
// Copyright 2021 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 <arpa/inet.h>
#include <fuchsia/gpu/agis/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/sys/cpp/component_context.h>
#include <lib/syslog/global.h>
#include <thread>
#include <gtest/gtest.h>
#include <sdk/lib/fdio/include/lib/fdio/fd.h>
namespace {
std::atomic<int> outstanding = 0;
zx_koid_t ProcessID() {
zx::unowned<zx::process> process = zx::process::self();
zx_info_handle_basic_t info;
zx_status_t status = zx_object_get_info(process->get(), ZX_INFO_HANDLE_BASIC, &info, sizeof(info),
nullptr /* actual */, nullptr /* avail */);
EXPECT_EQ(status, ZX_OK);
return info.koid;
}
std::string ProcessName() {
zx::unowned<zx::process> process = zx::process::self();
char process_name[ZX_MAX_NAME_LEN];
process->get_property(ZX_PROP_NAME, process_name, sizeof(process_name));
return process_name;
}
} // namespace
// AgisTest uses asynchronous FIDL entrypoints to provide reusable code for real
// world usage of the agis service.
class AgisTest : public testing::Test {
protected:
void SetUp() override {
num_connections_ = 0;
loop_ = std::make_unique<async::Loop>(&kAsyncLoopConfigAttachToCurrentThread);
std::unique_ptr<sys::ComponentContext> context = sys::ComponentContext::Create();
context->svc()->Connect(session_.NewRequest(loop_->dispatcher()));
session_.set_error_handler([this](zx_status_t status) {
FX_LOG(ERROR, "agis-test", "AgisTest::ErrHandler");
loop_->Quit();
});
process_id_ = ProcessID();
process_name_ = ProcessName();
}
void TearDown() override {
if (loop_) {
LoopWait();
loop_->Quit();
}
}
void LoopWait() {
while (outstanding) {
EXPECT_EQ(loop_->RunUntilIdle(), ZX_OK);
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
void Register(zx_koid_t process_id, std::string process_name) {
outstanding++;
session_->Register(
process_id, std::move(process_name),
[&](fuchsia::gpu::agis::Session_Register_Result result) {
fuchsia::gpu::agis::Session_Register_Response response(std::move(result.response()));
const auto status = result.err();
EXPECT_EQ(status, fuchsia::gpu::agis::Status::OK);
zx::handle socket = response.ResultValue_();
EXPECT_NE(socket.get(), 0ul) << "Null socket handle response";
int socket_fd = -1;
EXPECT_EQ(ZX_OK, fdio_fd_create(socket.release(), &socket_fd));
close(socket_fd);
outstanding--;
});
}
void Unregister(zx_koid_t process_id) {
outstanding++;
session_->Unregister(process_id, [&](fuchsia::gpu::agis::Session_Unregister_Result result) {
EXPECT_EQ(result.err(), fuchsia::gpu::agis::Status::OK);
outstanding--;
});
}
void Connections() {
outstanding++;
session_->Connections([&](fuchsia::gpu::agis::Session_Connections_Result result) {
fuchsia::gpu::agis::Session_Connections_Response response(std::move(result.response()));
const auto status = result.err();
EXPECT_EQ(status, fuchsia::gpu::agis::Status::OK);
switch (status) {
case fuchsia::gpu::agis::Status::OK: {
std::vector<fuchsia::gpu::agis::Connection> connections(response.ResultValue_());
for (auto &connection : connections) {
FX_LOGF(INFO, "agis-test", "AgisTest::Connection \"%lu\" \"%s\"",
connection.process_id(), connection.process_name().c_str());
}
num_connections_ = connections.size();
break;
}
default:
num_connections_ = 0;
break;
}
outstanding--;
});
}
std::unique_ptr<async::Loop> loop_;
fuchsia::gpu::agis::SessionPtr session_;
size_t num_connections_;
zx_koid_t process_id_;
std::string process_name_;
};
TEST_F(AgisTest, Register) {
Register(process_id_, process_name_);
outstanding++;
session_->Register(process_id_, process_name_,
[&](fuchsia::gpu::agis::Session_Register_Result result) {
EXPECT_EQ(result.err(), fuchsia::gpu::agis::Status::ALREADY_REGISTERED);
outstanding--;
});
Unregister(process_id_);
}
TEST_F(AgisTest, Unregister) {
Register(process_id_, process_name_);
Unregister(process_id_);
session_->Unregister(process_id_, [&](fuchsia::gpu::agis::Session_Unregister_Result result) {
const auto status = result.err();
EXPECT_EQ(status, fuchsia::gpu::agis::Status::NOT_FOUND);
});
}
TEST_F(AgisTest, Connections) {
Register(process_id_, process_name_);
Register(process_id_ + 1, process_name_ + "+1");
Connections();
LoopWait();
EXPECT_EQ(num_connections_, 2ul);
Unregister(process_id_);
Unregister(process_id_ + 1);
Connections();
LoopWait();
EXPECT_EQ(num_connections_, 0ul);
}
TEST_F(AgisTest, MaxConnections) {
uint32_t i = 0;
for (i = 0; i < fuchsia::gpu::agis::MAX_CONNECTIONS; i++) {
Register(process_id_ + i, process_name_ + "+" + std::to_string(i));
}
outstanding++;
session_->Register(process_id_ + i, process_name_ + "+" + std::to_string(i),
[&](fuchsia::gpu::agis::Session_Register_Result result) {
EXPECT_EQ(result.err(), fuchsia::gpu::agis::Status::CONNECTIONS_EXCEEDED);
outstanding--;
});
for (i = 0; i < fuchsia::gpu::agis::MAX_CONNECTIONS; i++) {
Unregister(process_id_ + i);
}
}
TEST_F(AgisTest, UsableSocket) {
// Register and retrieve the socket for the server.
int server_fd = -1;
outstanding++;
session_->Register(
process_id_, process_name_, [&](fuchsia::gpu::agis::Session_Register_Result result) {
fuchsia::gpu::agis::Session_Register_Response response(std::move(result.response()));
EXPECT_EQ(result.err(), fuchsia::gpu::agis::Status::OK);
zx::handle socket = response.ResultValue_();
EXPECT_EQ(ZX_OK, fdio_fd_create(socket.release(), &server_fd));
outstanding--;
});
LoopWait();
EXPECT_NE(server_fd, -1);
// Get the port assignment for the client.
outstanding++;
uint16_t port = 0;
std::string process_name;
session_->Connections([&](fuchsia::gpu::agis::Session_Connections_Result result) {
fuchsia::gpu::agis::Session_Connections_Response response(std::move(result.response()));
EXPECT_EQ(result.err(), fuchsia::gpu::agis::Status::OK);
std::vector<fuchsia::gpu::agis::Connection> connections(response.ResultValue_());
EXPECT_EQ(connections.size(), 1ul);
port = connections.front().port();
process_name = connections.front().process_name();
outstanding--;
});
LoopWait();
EXPECT_NE(port, 0);
EXPECT_EQ(process_name, process_name_);
// Start server listening on the socket retrieved from AGIS.
std::mutex mutex;
bool listening(false);
std::condition_variable condition;
const char message[] = "AGIS Client Message";
outstanding++;
std::thread server([&] {
{
std::unique_lock lock(mutex);
const int kMaxPendingQueueSize = 1;
const int listen_rv = listen(server_fd, kMaxPendingQueueSize);
ASSERT_EQ(listen_rv, 0) << "Listen failed with " << strerror(errno);
listening = true;
condition.notify_one();
}
// Accept connection from the client.
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
const int connection_fd =
accept(server_fd, reinterpret_cast<struct sockaddr *>(&client_addr), &client_addr_len);
ASSERT_NE(connection_fd, -1) << "Accept failed with: " << strerror(errno);
// Read client request.
char buffer[128] = {};
read(connection_fd, buffer, sizeof(buffer) - 1);
EXPECT_EQ(strcmp(buffer, message), 0);
close(connection_fd);
outstanding--;
});
// Create client connection and send a message to the server.
int client_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
ASSERT_NE(client_fd, -1) << "Socket failed with: " << strerror(errno);
struct sockaddr_in serv_addr = {};
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
inet_aton("127.0.0.1", &(serv_addr.sin_addr));
// Connect to the server.
{
std::unique_lock lock(mutex);
while (!listening) {
condition.wait(lock);
}
const int connect_rv =
connect(client_fd, reinterpret_cast<struct sockaddr *>(&serv_addr), sizeof(serv_addr));
ASSERT_EQ(connect_rv, 0) << "Connect failed with: " << strerror(errno);
}
// Send message to the server.
const ssize_t bytes_written = write(client_fd, message, strlen(message));
EXPECT_EQ(bytes_written, static_cast<ssize_t>(strlen(message)));
LoopWait();
close(client_fd);
close(server_fd);
server.join();
}
TEST(AgisDisconnect, Main) {
zx_koid_t process_id = ProcessID();
std::string process_name = ProcessName();
bool disconnect_outstanding = false;
auto loop = std::make_unique<async::Loop>(&kAsyncLoopConfigAttachToCurrentThread);
std::unique_ptr<sys::ComponentContext> context = sys::ComponentContext::Create();
auto loop_wait = [&disconnect_outstanding, &loop]() {
while (disconnect_outstanding) {
EXPECT_EQ(loop->RunUntilIdle(), ZX_OK);
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
};
// Create a Session, register |process_id| and verify its presence.
{
fuchsia::gpu::agis::SessionPtr session;
context->svc()->Connect(session.NewRequest(loop->dispatcher()));
session.set_error_handler([&loop](zx_status_t status) {
FX_LOGF(ERROR, "agis-test", "Register Disconnect ErrHandler - status %d", status);
if (loop) {
loop->Quit();
}
});
disconnect_outstanding = true;
session->Register(process_id, process_name,
[&](fuchsia::gpu::agis::Session_Register_Result result) {
EXPECT_EQ(result.err(), fuchsia::gpu::agis::Status::OK);
disconnect_outstanding = false;
});
loop_wait();
disconnect_outstanding = true;
session->Connections([&](fuchsia::gpu::agis::Session_Connections_Result result) {
fuchsia::gpu::agis::Session_Connections_Response response(std::move(result.response()));
EXPECT_EQ(result.err(), fuchsia::gpu::agis::Status::OK);
std::vector<fuchsia::gpu::agis::Connection> connections(response.ResultValue_());
EXPECT_EQ(connections.size(), 1ul);
bool found = false;
for (const auto &connection : connections) {
if (connection.process_id() == process_id) {
EXPECT_EQ(connection.process_name(), process_name);
found = true;
break;
}
}
EXPECT_TRUE(found);
disconnect_outstanding = false;
});
loop_wait();
}
// Create a new Session and verify that |process_id| is no longer registered.
fuchsia::gpu::agis::SessionPtr session;
context->svc()->Connect(session.NewRequest(loop->dispatcher()));
session.set_error_handler([&loop](zx_status_t status) {
FX_LOGF(ERROR, "agis-test", "Verify Disconnect ErrHandler - status %d", status);
if (loop) {
loop->Quit();
}
});
bool found = true;
while (found) {
disconnect_outstanding = true;
session->Connections([&](fuchsia::gpu::agis::Session_Connections_Result result) {
EXPECT_EQ(result.err(), fuchsia::gpu::agis::Status::OK);
auto connections(result.response().ResultValue_());
bool component_found = false;
for (const auto &connection : connections) {
if (connection.process_id() == process_id) {
EXPECT_EQ(connection.process_name(), process_name);
component_found = true;
break;
}
}
found = component_found;
disconnect_outstanding = false;
});
loop_wait();
}
// Self-documenting no-op.
EXPECT_FALSE(found);
loop->Quit();
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}