| /* | |
| * 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; | |
| }; |