diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 34ab078fc2..eede4b4c42 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -2400,4 +2400,40 @@ if(BUILD_TESTING) ${CMAKE_CURRENT_BINARY_DIR}/no-such-libobjc.dylib) set_tests_properties(host_performance_mac_activity PROPERTIES TIMEOUT 20) endif() + add_test_executable(host_performance_process_tests + ${LIBS_INCLUDE_DIR}/tests/HostPerformanceProcess.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/System/src/HostPerformance.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/System/src/HostPerformanceProcess.cpp) + target_include_directories(host_performance_process_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(host_performance_process_tests PRIVATE libc Threads::Threads ${CMAKE_DL_LIBS}) + set_target_properties(host_performance_process_tests PROPERTIES CXX_EXTENSIONS OFF) + configure_windows_unwind(host_performance_process_tests) + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + target_sources(host_performance_process_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/System/src/GameModeClient.cpp) + elseif(APPLE) + target_sources(host_performance_process_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/System/src/MacActivity.cpp) + endif() + function(add_host_performance_process_test name pattern) + add_test(NAME host_performance_process_${name} COMMAND host_performance_process_tests ${ARGN}) + set_tests_properties(host_performance_process_${name} PROPERTIES TIMEOUT 20 PASS_REGULAR_EXPRESSION "${pattern}") + if(WIN32) + set_tests_properties(host_performance_process_${name} PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() + endfunction() + add_host_performance_process_test(exit "begin called.release ran at exit" exit) + add_host_performance_process_test(exit_without_handlers "begin called.release ran at exit" exit-without-handlers) + add_host_performance_process_test(refused "begin called" refused) + set_tests_properties(host_performance_process_refused PROPERTIES FAIL_REGULAR_EXPRESSION "release ran") + add_host_performance_process_test(off "off left the backend alone" off) + set_tests_properties(host_performance_process_off PROPERTIES FAIL_REGULAR_EXPRESSION "begin called") + add_host_performance_process_test(twice "second session refused" twice) + add_host_performance_process_test(environment "environment accepted" environment) + add_test(NAME host_performance_process_invalid COMMAND host_performance_process_tests environment) + set_tests_properties(host_performance_process_invalid PROPERTIES TIMEOUT 20 ENVIRONMENT "APS5_GAME_MODE=bogus" + PASS_REGULAR_EXPRESSION "invalid value rejected by name") + if(WIN32) + set_tests_properties(host_performance_process_invalid PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() endif() diff --git a/core/libs/prx/libkernel/System/src/HostPerformanceProcess.cpp b/core/libs/prx/libkernel/System/src/HostPerformanceProcess.cpp new file mode 100644 index 0000000000..16deb97f3a --- /dev/null +++ b/core/libs/prx/libkernel/System/src/HostPerformanceProcess.cpp @@ -0,0 +1,68 @@ +#include "prx/libkernel/System/include/HostPerformance.hpp" +#include "prx/libc/include/Shutdown.hpp" +#include +#include +#include +#include +#include + +#if defined(__linux__) +#include "prx/libkernel/System/include/GameModeClient.hpp" +#elif defined(__APPLE__) +#include "prx/libkernel/System/include/MacActivity.hpp" +#endif + +namespace HostPerformance { + +namespace { + +#if !defined(__linux__) && !defined(__APPLE__) +class UnsupportedBackend final : public IBackend { +public: + const char* Name() const override { return "host-performance"; } + BeginResult Begin() override { return {Outcome::Unavailable, "this host has no game mode request"}; } + void End() override {} +}; +#endif + +struct ProcessSession { + std::unique_ptr Backend; + Session Held; + + ProcessSession(Mode mode, std::unique_ptr backend) + : Backend(std::move(backend)), Held(mode, *Backend, [](const std::string& line) { std::fprintf(stderr, "[host-performance] %s\n", line.c_str()); }) {} +}; + +ProcessSession* process = nullptr; + +void StopProcessSession() { + if (process != nullptr) process->Held.Stop(); +} + +} + +std::unique_ptr CreateHostBackend() { +#if defined(__linux__) + return std::make_unique(); +#elif defined(__APPLE__) + return std::make_unique(); +#else + return std::make_unique(); +#endif +} + +Session* StartProcessSession(Mode mode, std::unique_ptr backend) { + if (process != nullptr) throw std::logic_error("host performance: the process session already exists"); + if (mode == Mode::Off) return nullptr; + process = new ProcessSession(mode, std::move(backend)); + if (process->Held.Start() == State::Active) LibcRegisterShutdown_nid_postfix(StopProcessSession); + return &process->Held; +} + +void StartFromEnvironment() { + const Mode mode = ParseMode(std::getenv("APS5_GAME_MODE")); + if (mode == Mode::Off) return; + StartProcessSession(mode, CreateHostBackend()); +} + +} diff --git a/core/libs/tests/HostPerformanceProcess.cpp b/core/libs/tests/HostPerformanceProcess.cpp new file mode 100644 index 0000000000..fd59efbc51 --- /dev/null +++ b/core/libs/tests/HostPerformanceProcess.cpp @@ -0,0 +1,87 @@ +#include "prx/libc/include/Shutdown.hpp" +#include "prx/libkernel/System/include/HostPerformance.hpp" +#include +#include +#include +#include +#include +#include + +namespace { + +using namespace HostPerformance; + +class PrintingBackend final : public IBackend { +public: + explicit PrintingBackend(Outcome outcome) : outcome(outcome) {} + const char* Name() const override { return "printing"; } + + BeginResult Begin() override { + std::puts("begin called"); + return {outcome, "scripted"}; + } + + void End() override { + std::puts("release ran at exit"); + std::fflush(stdout); + } + +private: + Outcome outcome; +}; + +int Fail(const char* message) { + std::fprintf(stderr, "host performance process: %s\n", message); + return 1; +} + +} + +int main(int argc, char** argv) { + if (argc != 2) return Fail("usage: host_performance_process_tests "); + const std::string name = argv[1]; + if (name == "exit" || name == "exit-without-handlers") { + if (StartProcessSession(Mode::On, std::make_unique(Outcome::Started)) == nullptr) return Fail("an on session must exist"); + std::fflush(stdout); + if (name == "exit") LibcExit_nid_no_patch(0); + LibcExitWithoutHandlers_nid_no_patch(0); + } + if (name == "refused") { + if (StartProcessSession(Mode::Auto, std::make_unique(Outcome::Refused)) == nullptr) return Fail("an auto session must exist"); + std::fflush(stdout); + LibcExit_nid_no_patch(0); + } + if (name == "off") { + if (StartProcessSession(Mode::Off, std::make_unique(Outcome::Started)) != nullptr) return Fail("an off session must not exist"); + std::puts("off left the backend alone"); + std::fflush(stdout); + LibcExit_nid_no_patch(0); + } + if (name == "twice") { + StartProcessSession(Mode::On, std::make_unique(Outcome::Started)); + try { + StartProcessSession(Mode::On, std::make_unique(Outcome::Started)); + } catch (const std::logic_error&) { + std::puts("second session refused"); + std::fflush(stdout); + LibcExit_nid_no_patch(0); + } + return Fail("a second session must be refused"); + } + if (name == "environment") { + const char* value = std::getenv("APS5_GAME_MODE"); + try { + StartFromEnvironment(); + } catch (const std::invalid_argument& error) { + if (value != nullptr && std::strcmp(value, "bogus") == 0 && std::string(error.what()).find("APS5_GAME_MODE=bogus") != std::string::npos) { + std::puts("invalid value rejected by name"); + return 0; + } + return Fail(error.what()); + } + if (value != nullptr && std::strcmp(value, "bogus") == 0) return Fail("an invalid value must be rejected"); + std::puts("environment accepted"); + return 0; + } + return Fail("unknown case"); +}