blob: 192fb206208d8ff157164fdd271b8aff7b78b8b2 [file] [log] [blame] [edit]
/*
* Copyright 2024 Rive
*/
#pragma once
#include "common/tcp_client.hpp"
#include "common/queue.hpp"
#include "write_png_file.hpp"
#include <array>
#include <cassert>
#include <string>
#include <thread>
#include <vector>
#include <filesystem>
struct ImageSaveArgs
{
std::string name;
uint32_t width;
uint32_t height;
std::vector<uint8_t> pixels;
bool quit = false;
};
// Attempts to connect to the python server, and if successful, pipes stdout and
// stderr to the server. Also provides utilities for encoding & uploading PNGs,
// and notifying the server when the application crashes.
class TestHarness
{
public:
static TestHarness& Instance();
~TestHarness() { shutdown(); }
bool initialized() const { return m_initialized; }
void init(std::unique_ptr<TCPClient>, size_t pngThreadCount);
void init(std::filesystem::path, size_t pngThreadCount);
bool hasTCPConnection() const { return m_primaryTCPClient != nullptr; }
void setPNGCompression(PNGCompression compression)
{
m_pngCompression = compression;
}
void savePNG(ImageSaveArgs args);
// Only returns true the on the first server request for a given name.
// Prevents gms from running more than once in a multi-process execution.
bool claimGMTest(const std::string&);
// Downloads the next .riv file to test. (Must only be run on the main
// thread.)
bool fetchRivFile(std::string& name, std::vector<uint8_t>& bytes);
// Sends a message to be printed on the server's stdout, e.g., for
// forwarding the client's stdout.
void printMessageOnServer(const char* msg);
// Returns true if there is an input character to process from the server.
bool peekChar(char& key);
void shutdown();
void onApplicationCrash(const char* message);
private:
TestHarness();
// Pipe stdout & stderr and spin off a thread that forwards them to the test
// harness and, on Android, the Android logs.
void initStdioThread();
// Adheres to a quick-and-dirty protocol for sending a PNG image back to the
// python harness.
void sendImage(const std::string& remoteDestination,
uint32_t width,
uint32_t height,
uint8_t* imageDataRGBA);
// Print a message on the server-side console.
void sendConsoleMessage(const char* message, uint32_t messageLength);
void shutdownStdioThread();
void shutdownInputPumpThread();
// Forwards stdout and stderr to the server.
static void MonitorStdIOThread(void* thisPtr)
{
reinterpret_cast<TestHarness*>(thisPtr)->monitorStdIOThread();
}
void monitorStdIOThread();
// Encodes PNGS and sends them to the server.
static void EncodePNGThread(void* thisPtr)
{
reinterpret_cast<TestHarness*>(thisPtr)->encodePNGThread();
}
void encodePNGThread();
// Receives standard input from the server and queues it up.
static void InputPumpThread(void* thisPtr)
{
reinterpret_cast<TestHarness*>(thisPtr)->inputPumpThread();
}
void inputPumpThread();
bool m_initialized = false;
std::unique_ptr<TCPClient> m_primaryTCPClient;
std::filesystem::path m_outputDir;
// Forwarding stdout and stderr to the server.
int m_savedStdout = 0;
int m_savedStderr = 0;
std::array<int, 2> m_stdioPipe = {0, 0};
std::thread m_stdioThread;
// PNG image encode queue.
queue<ImageSaveArgs> m_encodeQueue{1};
PNGCompression m_pngCompression = PNGCompression::compact;
std::vector<std::thread> m_encodeThreads;
// Standard input from the server.
queue<char> m_inputQueue{128};
std::thread m_inputPumpThread;
};