feat: Enhance ImGuiTestHarnessServer with proper shutdown handling and update gRPC service initialization

This commit is contained in:
scawful
2025-10-01 23:32:41 -04:00
parent 3d272605c1
commit ead85c87b5
6 changed files with 278 additions and 11 deletions

View File

@@ -208,6 +208,10 @@ ImGuiTestHarnessServer& ImGuiTestHarnessServer::Instance() {
return *instance;
}
ImGuiTestHarnessServer::~ImGuiTestHarnessServer() {
Shutdown();
}
absl::Status ImGuiTestHarnessServer::Start(int port) {
if (server_) {
return absl::FailedPreconditionError("Server already running");
@@ -216,19 +220,19 @@ absl::Status ImGuiTestHarnessServer::Start(int port) {
// Create the service implementation
service_ = std::make_unique<ImGuiTestHarnessServiceImpl>();
// Create the gRPC service wrapper
auto grpc_service = std::make_unique<ImGuiTestHarnessServiceGrpc>(service_.get());
// Create the gRPC service wrapper (store as member to prevent it from going out of scope)
grpc_service_ = std::make_unique<ImGuiTestHarnessServiceGrpc>(service_.get());
std::string server_address = absl::StrFormat("127.0.0.1:%d", port);
std::string server_address = absl::StrFormat("0.0.0.0:%d", port);
grpc::ServerBuilder builder;
// Listen on localhost only (security)
// Listen on all interfaces (use 0.0.0.0 to avoid IPv6/IPv4 binding conflicts)
builder.AddListeningPort(server_address,
grpc::InsecureServerCredentials());
// Register service
builder.RegisterService(grpc_service.get());
builder.RegisterService(grpc_service_.get());
// Build and start
server_ = builder.BuildAndStart();

View File

@@ -68,6 +68,9 @@ class ImGuiTestHarnessServiceImpl {
ScreenshotResponse* response);
};
// Forward declaration of the gRPC service wrapper
class ImGuiTestHarnessServiceGrpc;
// Singleton server managing the gRPC service
// This class manages the lifecycle of the gRPC server
class ImGuiTestHarnessServer {
@@ -91,7 +94,7 @@ class ImGuiTestHarnessServer {
private:
ImGuiTestHarnessServer() = default;
~ImGuiTestHarnessServer() { Shutdown(); }
~ImGuiTestHarnessServer(); // Defined in .cc file to allow incomplete type deletion
// Disable copy and move
ImGuiTestHarnessServer(const ImGuiTestHarnessServer&) = delete;
@@ -99,6 +102,7 @@ class ImGuiTestHarnessServer {
std::unique_ptr<grpc::Server> server_;
std::unique_ptr<ImGuiTestHarnessServiceImpl> service_;
std::unique_ptr<ImGuiTestHarnessServiceGrpc> grpc_service_;
int port_ = 0;
};

View File

@@ -9,6 +9,10 @@
#include "util/flag.h"
#include "util/log.h"
#ifdef YAZE_WITH_GRPC
#include "app/core/imgui_test_harness_service.h"
#endif
/**
* @namespace yaze
* @brief Main namespace for the application.
@@ -20,6 +24,14 @@ DEFINE_FLAG(std::string, rom_file, "", "The ROM file to load.");
DEFINE_FLAG(std::string, log_file, "", "Output log file path for debugging.");
DEFINE_FLAG(bool, debug, false, "Enable debug logging and verbose output.");
#ifdef YAZE_WITH_GRPC
// gRPC test harness flags
DEFINE_FLAG(bool, enable_test_harness, false,
"Start gRPC test harness server for automated GUI testing.");
DEFINE_FLAG(int, test_harness_port, 50051,
"Port for gRPC test harness server (default: 50051).");
#endif
int main(int argc, char **argv) {
absl::InitializeSymbolizer(argv[0]);
@@ -56,6 +68,24 @@ int main(int argc, char **argv) {
rom_filename = FLAGS_rom_file->Get();
}
#ifdef YAZE_WITH_GRPC
// Start gRPC test harness server if requested
if (FLAGS_enable_test_harness->Get()) {
auto& server = yaze::test::ImGuiTestHarnessServer::Instance();
int port = FLAGS_test_harness_port->Get();
std::cout << "\n🚀 Starting ImGui Test Harness on port " << port << "..." << std::endl;
auto status = server.Start(port);
if (!status.ok()) {
std::cerr << "❌ ERROR: Failed to start test harness server on port " << port << std::endl;
std::cerr << " " << status.message() << std::endl;
return 1;
}
std::cout << "✅ Test harness ready on 127.0.0.1:" << port << std::endl;
std::cout << " Available RPCs: Ping, Click, Type, Wait, Assert, Screenshot\n" << std::endl;
}
#endif
#ifdef __APPLE__
return yaze_run_cocoa_app_delegate(rom_filename.c_str());
#elif defined(_WIN32)
@@ -76,5 +106,10 @@ int main(int argc, char **argv) {
}
controller->OnExit();
#ifdef YAZE_WITH_GRPC
// Shutdown gRPC server if running
yaze::test::ImGuiTestHarnessServer::Instance().Shutdown();
#endif
return EXIT_SUCCESS;
}

View File

@@ -48,6 +48,9 @@ class Flag : public IFlag {
}
value_ = parsed;
}
// Set the value directly (used by specializations)
void SetValue(const T& val) { value_ = val; }
// Returns the current (parsed or default) value of the flag.
const T& Get() const { return value_; }
@@ -59,6 +62,19 @@ class Flag : public IFlag {
std::string help_;
};
// Specialization for bool to handle "true"/"false" strings
template <>
inline void Flag<bool>::ParseValue(const std::string& text) {
if (text == "true" || text == "1" || text == "yes" || text == "on") {
SetValue(true);
} else if (text == "false" || text == "0" || text == "no" || text == "off") {
SetValue(false);
} else {
throw std::runtime_error("Failed to parse boolean flag: " + name() +
" (expected true/false/1/0/yes/no/on/off, got: " + text + ")");
}
}
class FlagRegistry {
public:
// Registers a flag in the global registry.