blob: 837373033207077964ee1bc21f7c3ad8f5c17cee [file] [log] [blame]
// 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/cpp/log_settings.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zxio/zxio.h>
#include <thread>
#include <gtest/gtest.h>
namespace {
std::atomic<int> outstanding = 0;
zx_koid_t ProcessKoid() {
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;
}
uint64_t TimeMS() {
return std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
}
} // namespace
class AgisTest : public testing::Test {
protected:
void SetUp() override {
num_vtcs_ = 0;
loop_ = std::make_unique<async::Loop>(&kAsyncLoopConfigAttachToCurrentThread);
std::unique_ptr<sys::ComponentContext> context = sys::ComponentContext::Create();
zx_status_t status =
context->svc()->Connect(component_registry_.NewRequest(loop_->dispatcher()));
EXPECT_EQ(status, ZX_OK);
component_registry_.set_error_handler([this](zx_status_t status) {
FX_SLOG(ERROR, "|component_registry_| error handler", FX_KV("status", status));
loop_->Quit();
ASSERT_TRUE(false);
});
status = context->svc()->Connect(observer_.NewRequest(loop_->dispatcher()));
EXPECT_EQ(status, ZX_OK);
observer_.set_error_handler([this](zx_status_t status) {
FX_SLOG(ERROR, "agis-test: |observer_| error handler");
loop_->Quit();
ASSERT_TRUE(false);
});
status = context->svc()->Connect(connector_.NewRequest(loop_->dispatcher()));
EXPECT_EQ(status, ZX_OK);
connector_.set_error_handler([this](zx_status_t status) {
FX_SLOG(ERROR, "agis-test: |connector_| error handler");
loop_->Quit();
ASSERT_TRUE(false);
});
process_koid_ = ProcessKoid();
process_name_ = ProcessName();
client_id_ = TimeMS();
}
void TearDown() override {
if (loop_) {
LoopWait();
loop_->Quit();
}
}
// LoopWait() guarantees that the callback supplied to any interface in the agis protocol
// library has been called and completed. The callbacks compute / verify return results
// from the protocol methods.
void LoopWait(const uint64_t timeout_secs = 0) {
uint64_t elapsed_millis = 0;
const uint64_t timeout_millis = timeout_secs * 1000;
while (outstanding && (timeout_secs == 0 || elapsed_millis < timeout_millis)) {
loop_->RunUntilIdle();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
elapsed_millis += 50;
}
}
void Register(uint64_t id, zx_koid_t process_koid, std::string process_name) {
outstanding++;
component_registry_->Register(
id, process_koid, std::move(process_name),
[&](fuchsia::gpu::agis::ComponentRegistry_Register_Result result) {
EXPECT_FALSE(result.err());
outstanding--;
});
LoopWait();
}
void Unregister(uint64_t client_id) {
outstanding++;
component_registry_->Unregister(
client_id, [&](fuchsia::gpu::agis::ComponentRegistry_Unregister_Result result) {
EXPECT_FALSE(result.err());
outstanding--;
});
}
void Vtcs() {
outstanding++;
observer_->Vtcs([&](fuchsia::gpu::agis::Observer_Vtcs_Result result) {
fuchsia::gpu::agis::Observer_Vtcs_Response response(std::move(result.response()));
EXPECT_FALSE(result.err());
std::vector<fuchsia::gpu::agis::Vtc> vtcs(response.ResultValue_());
num_vtcs_ = vtcs.size();
outstanding--;
});
}
//
// Ivars.
//
std::unique_ptr<async::Loop> loop_;
fuchsia::gpu::agis::ComponentRegistryPtr component_registry_;
fuchsia::gpu::agis::ObserverPtr observer_;
fuchsia::gpu::agis::ConnectorPtr connector_;
size_t num_vtcs_;
zx_koid_t process_koid_;
std::string process_name_;
uint64_t client_id_;
};
// Test register.
TEST_F(AgisTest, Register) {
Register(client_id_, process_koid_, process_name_);
outstanding++;
component_registry_->Register(client_id_, process_koid_, process_name_,
[&](fuchsia::gpu::agis::ComponentRegistry_Register_Result result) {
EXPECT_EQ(result.err(),
fuchsia::gpu::agis::Error::ALREADY_REGISTERED);
outstanding--;
});
LoopWait();
Unregister(client_id_);
LoopWait();
}
// Test unregister.
TEST_F(AgisTest, Unregister) {
Register(client_id_, process_koid_, process_name_);
Unregister(client_id_);
LoopWait();
outstanding++;
component_registry_->Unregister(
client_id_, [&](fuchsia::gpu::agis::ComponentRegistry_Unregister_Result result) {
EXPECT_TRUE(result.is_err());
EXPECT_EQ(result.err(), fuchsia::gpu::agis::Error::NOT_FOUND);
outstanding--;
});
LoopWait();
}
// Test vtc list cardinality.
TEST_F(AgisTest, Vtcs) {
Register(client_id_, process_koid_, process_name_);
Register(client_id_ + 1, process_koid_, process_name_ + "+1");
Vtcs();
LoopWait();
EXPECT_EQ(num_vtcs_, 2ul);
Unregister(client_id_);
Unregister(client_id_ + 1);
LoopWait();
Vtcs();
LoopWait();
EXPECT_EQ(num_vtcs_, 0ul);
}
// Test registry overflow.
TEST_F(AgisTest, MaxVtcs) {
uint32_t i = 0;
for (i = 0; i < fuchsia::gpu::agis::MAX_VTCS; i++) {
Register(client_id_ + i, process_koid_, process_name_ + "+" + std::to_string(i));
}
outstanding++;
component_registry_->Register(client_id_ + i, process_koid_,
process_name_ + "+" + std::to_string(i),
[&](fuchsia::gpu::agis::ComponentRegistry_Register_Result result) {
EXPECT_EQ(result.err(), fuchsia::gpu::agis::Error::VTCS_EXCEEDED);
outstanding--;
});
LoopWait();
for (i = 0; i < fuchsia::gpu::agis::MAX_VTCS; i++) {
Unregister(client_id_ + i);
}
LoopWait();
}
// Validate retrieved socket.
TEST_F(AgisTest, UsableSocket) {
outstanding++;
component_registry_->Register(client_id_, process_koid_, process_name_,
[&](fuchsia::gpu::agis::ComponentRegistry_Register_Result result) {
EXPECT_FALSE(result.err());
outstanding--;
});
LoopWait();
// Issue hanging get for the vulkan-end socket. Notice there is no following LoopWait() call.
zx::socket vulkan_socket;
component_registry_->GetVulkanSocket(
client_id_, [&](fuchsia::gpu::agis::ComponentRegistry_GetVulkanSocket_Result result) {
fuchsia::gpu::agis::ComponentRegistry_GetVulkanSocket_Response response(
std::move(result.response()));
EXPECT_FALSE(result.err());
vulkan_socket = response.ResultValue_();
EXPECT_TRUE(vulkan_socket.is_valid());
});
std::this_thread::sleep_for(std::chrono::milliseconds(50));
loop_->RunUntilIdle();
EXPECT_FALSE(vulkan_socket.is_valid());
uint32_t global_id = 0;
std::string process_name;
zx_koid_t process_koid = 0;
outstanding++;
observer_->Vtcs([&](fuchsia::gpu::agis::Observer_Vtcs_Result result) {
fuchsia::gpu::agis::Observer_Vtcs_Response response(std::move(result.response()));
EXPECT_FALSE(result.err());
std::vector<fuchsia::gpu::agis::Vtc> vtcs(response.ResultValue_());
EXPECT_EQ(vtcs.size(), 1ul);
global_id = vtcs.front().global_id();
process_koid = vtcs.front().process_koid();
process_name = vtcs.front().process_name();
outstanding--;
});
LoopWait();
EXPECT_EQ(process_name, process_name_);
EXPECT_EQ(process_koid, process_koid_);
EXPECT_NE(global_id, 0u);
// Explicitly retrieve the ffx-end socket and implicitly satisfy the hanging GetVulkanSocket.
outstanding++;
zx::socket ffx_socket;
connector_->GetSocket(global_id, [&](fuchsia::gpu::agis::Connector_GetSocket_Result result) {
fuchsia::gpu::agis::Connector_GetSocket_Response response(std::move(result.response()));
EXPECT_FALSE(result.err());
ffx_socket = response.ResultValue_();
outstanding--;
});
while (!vulkan_socket.is_valid() || !ffx_socket.is_valid()) {
LoopWait(30);
}
// Send the message from the vulkan end.
const char message[] = "AGIS Server Message";
size_t actual = 0;
zx_status_t status = vulkan_socket.write(0u, message, sizeof(message), &actual);
ASSERT_EQ(status, ZX_OK);
EXPECT_EQ(actual, sizeof(message));
// Read the message from the ffx end.
char buffer[sizeof(message)];
actual = 0;
status = ffx_socket.read(0u, &buffer, sizeof(buffer), &actual);
ASSERT_EQ(status, ZX_OK);
EXPECT_EQ(actual, sizeof(message));
EXPECT_EQ(strcmp(message, buffer), 0);
}
// Test multiple retrievals of the vulkan socket from the same registration.
TEST_F(AgisTest, Reget) {
Register(client_id_, process_koid_, process_name_);
// Issue hanging get for the vulkan-end socket. Notice there is no following LoopWait() call.
zx::socket vulkan_socket;
component_registry_->GetVulkanSocket(
client_id_, [&](fuchsia::gpu::agis::ComponentRegistry_GetVulkanSocket_Result result) {
fuchsia::gpu::agis::ComponentRegistry_GetVulkanSocket_Response response(
std::move(result.response()));
EXPECT_FALSE(result.err());
vulkan_socket = response.ResultValue_();
});
std::this_thread::sleep_for(std::chrono::milliseconds(50));
loop_->RunUntilIdle();
EXPECT_FALSE(vulkan_socket.is_valid());
outstanding++;
uint32_t global_id = 0;
std::string process_name;
zx_koid_t process_koid = 0;
observer_->Vtcs([&](fuchsia::gpu::agis::Observer_Vtcs_Result result) {
fuchsia::gpu::agis::Observer_Vtcs_Response response(std::move(result.response()));
EXPECT_FALSE(result.err());
std::vector<fuchsia::gpu::agis::Vtc> vtcs(response.ResultValue_());
EXPECT_EQ(vtcs.size(), 1ul);
global_id = vtcs.front().global_id();
process_name = vtcs.front().process_name();
process_koid = vtcs.front().process_koid();
outstanding--;
});
LoopWait();
// Explicitly retrieve the ffx-end socket and implicitly satisfy the hanging GetVulkanSocket.
outstanding++;
zx::socket ffx_socket;
connector_->GetSocket(global_id, [&](fuchsia::gpu::agis::Connector_GetSocket_Result result) {
fuchsia::gpu::agis::Connector_GetSocket_Response response(std::move(result.response()));
EXPECT_FALSE(result.err());
ffx_socket = response.ResultValue_();
outstanding--;
});
while (!vulkan_socket.is_valid() || !ffx_socket.is_valid()) {
LoopWait(30);
}
// Re-get the socket.
vulkan_socket.reset();
component_registry_->GetVulkanSocket(
client_id_, [&](fuchsia::gpu::agis::ComponentRegistry_GetVulkanSocket_Result result) {
fuchsia::gpu::agis::ComponentRegistry_GetVulkanSocket_Response response(
std::move(result.response()));
EXPECT_FALSE(result.err());
vulkan_socket = response.ResultValue_();
});
outstanding++;
connector_->GetSocket(global_id, [&](fuchsia::gpu::agis::Connector_GetSocket_Result result) {
fuchsia::gpu::agis::Connector_GetSocket_Response response(std::move(result.response()));
EXPECT_FALSE(result.err());
ffx_socket = response.ResultValue_();
outstanding--;
});
while (!vulkan_socket.is_valid() || !ffx_socket.is_valid()) {
LoopWait(30);
}
// Send the message from the vulkan end.
const char message[] = "AGIS Server Message";
size_t actual = 0;
auto status = vulkan_socket.write(0u, message, sizeof(message), &actual);
ASSERT_EQ(status, ZX_OK);
EXPECT_EQ(actual, sizeof(message));
// Read the message from the ffx end.
char buffer[sizeof(message)];
actual = 0;
status = ffx_socket.read(0u, &buffer, sizeof(buffer), &actual);
ASSERT_EQ(status, ZX_OK);
EXPECT_EQ(actual, sizeof(message));
EXPECT_EQ(strcmp(message, buffer), 0);
Unregister(client_id_);
LoopWait();
}
// Test GetSocket followed by GetVulkanSocket retrieval.
TEST_F(AgisTest, ReverseGet) {
Register(client_id_, process_koid_, process_name_);
outstanding++;
uint32_t global_id = 0;
std::string process_name;
zx_koid_t process_koid = 0;
observer_->Vtcs([&](fuchsia::gpu::agis::Observer_Vtcs_Result result) {
fuchsia::gpu::agis::Observer_Vtcs_Response response(std::move(result.response()));
EXPECT_FALSE(result.err());
std::vector<fuchsia::gpu::agis::Vtc> vtcs(response.ResultValue_());
EXPECT_EQ(vtcs.size(), 1ul);
global_id = vtcs.front().global_id();
process_name = vtcs.front().process_name();
process_koid = vtcs.front().process_koid();
outstanding--;
});
LoopWait();
// Retrieve the ffx socket end first.
outstanding++;
zx::socket ffx_socket;
connector_->GetSocket(global_id, [&](fuchsia::gpu::agis::Connector_GetSocket_Result result) {
fuchsia::gpu::agis::Connector_GetSocket_Response response(std::move(result.response()));
EXPECT_FALSE(result.err());
ffx_socket = response.ResultValue_();
outstanding--;
});
// Retrieve the vulkan socket end second.
outstanding++;
zx::socket vulkan_socket;
component_registry_->GetVulkanSocket(
client_id_, [&](fuchsia::gpu::agis::ComponentRegistry_GetVulkanSocket_Result result) {
fuchsia::gpu::agis::ComponentRegistry_GetVulkanSocket_Response response(
std::move(result.response()));
EXPECT_FALSE(result.err());
vulkan_socket = response.ResultValue_();
outstanding--;
});
while (!vulkan_socket.is_valid() || !ffx_socket.is_valid()) {
LoopWait(30);
}
// Send the message from the vulkan end.
const char message[] = "AGIS Server Message";
size_t actual = 0;
auto status = vulkan_socket.write(0u, message, sizeof(message), &actual);
ASSERT_EQ(status, ZX_OK);
EXPECT_EQ(actual, sizeof(message));
// Read the message from the ffx end.
char buffer[sizeof(message)];
actual = 0;
status = ffx_socket.read(0u, &buffer, sizeof(buffer), &actual);
ASSERT_EQ(status, ZX_OK);
EXPECT_EQ(actual, sizeof(message));
EXPECT_EQ(strcmp(message, buffer), 0);
Unregister(client_id_);
LoopWait();
}
// Test unexpected disconnects / shutdowns.
TEST(AgisDisconnect, Main) {
zx_koid_t process_koid = ProcessKoid();
std::string process_name = ProcessName();
uint64_t client_id = TimeMS();
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 component_registry, register |client_id| and verify its presence.
{
fuchsia::gpu::agis::ComponentRegistryPtr component_registry;
context->svc()->Connect(component_registry.NewRequest(loop->dispatcher()));
component_registry.set_error_handler([&loop](zx_status_t status) {
FX_SLOG(ERROR, "Register Disconnect ComponentRegistry ErrHandler", FX_KV("status", status));
if (loop) {
loop->Quit();
}
});
disconnect_outstanding = true;
component_registry->Register(client_id, process_koid, process_name,
[&](fuchsia::gpu::agis::ComponentRegistry_Register_Result result) {
EXPECT_FALSE(result.err());
disconnect_outstanding = false;
});
loop_wait();
fuchsia::gpu::agis::ObserverPtr observer;
context->svc()->Connect(observer.NewRequest(loop->dispatcher()));
observer.set_error_handler([&loop](zx_status_t status) {
FX_SLOG(ERROR, "Register Disconnect Observer ErrHandler", FX_KV("status", status));
if (loop) {
loop->Quit();
}
});
disconnect_outstanding = true;
observer->Vtcs([&](fuchsia::gpu::agis::Observer_Vtcs_Result result) {
fuchsia::gpu::agis::Observer_Vtcs_Response response(std::move(result.response()));
EXPECT_FALSE(result.err());
std::vector<fuchsia::gpu::agis::Vtc> vtcs(response.ResultValue_());
EXPECT_EQ(vtcs.size(), 1ul);
bool found = false;
for (const auto &vtc : vtcs) {
if (vtc.process_koid() == process_koid) {
EXPECT_EQ(vtc.process_name(), process_name);
found = true;
break;
}
}
EXPECT_TRUE(found);
disconnect_outstanding = false;
});
loop_wait();
}
// Create a new observer and verify that |process_koid| is no longer registered.
fuchsia::gpu::agis::ObserverPtr observer;
context->svc()->Connect(observer.NewRequest(loop->dispatcher()));
observer.set_error_handler([&loop](zx_status_t status) {
FX_SLOG(ERROR, "Verify Disconnect Observer ErrHandler ", FX_KV("status", status));
if (loop) {
loop->Quit();
}
});
bool found = true;
while (found) {
disconnect_outstanding = true;
observer->Vtcs([&](fuchsia::gpu::agis::Observer_Vtcs_Result result) {
EXPECT_FALSE(result.err());
auto vtcs(result.response().ResultValue_());
bool component_found = false;
for (const auto &vtc : vtcs) {
if (vtc.process_koid() == process_koid) {
EXPECT_EQ(vtc.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);
fuchsia_logging::SetTags({"agis-test"});
return RUN_ALL_TESTS();
}