// Modified by vii from komaruworld/mocktail. See README "About this fork". #include "runtime/runtime_config_file.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "runtime/runtime_config_bootstrap.h" #ifndef MOCKTAIL_TEST_SOURCE_DIR #error "MOCKTAIL_TEST_SOURCE_DIR must point at the Mocktail source tree" #endif namespace mocktail { namespace runtime { namespace { class MapEnvironment final : public Environment { public: explicit MapEnvironment( std::unordered_map values = {}) : values_(std::move(values)) {} std::optional Get(std::string_view name) const override { const auto found = values_.find(std::string(name)); return found == values_.end() ? std::nullopt : std::optional(found->second); } private: std::unordered_map values_; }; class TemporaryDirectory { public: TemporaryDirectory() { char pattern[] = "/tmp/mocktail_runtime_config_XXXXXX"; char* created = mkdtemp(pattern); if (created != nullptr) { path_ = created; } } ~TemporaryDirectory() { std::error_code error; std::filesystem::remove_all(path_, error); } std::filesystem::path Write(std::string_view contents) const { const std::filesystem::path file = path_ / "config.yaml"; std::ofstream output(file); output << contents; return file; } const std::filesystem::path& path() const { return path_; } private: std::filesystem::path path_; }; std::string ReadFile(const std::filesystem::path& path) { std::ifstream input(path, std::ios::binary); return std::string(std::istreambuf_iterator(input), std::istreambuf_iterator()); } TEST(RuntimeConfigBootstrapTest, CreatesCompletePrivateFirstRunFile) { TemporaryDirectory temporary; const std::filesystem::path file = temporary.path() / "nested/mocktail/config.yaml"; const mode_t previous_umask = umask(0); const RuntimeConfigBootstrapResult bootstrapped = EnsureRuntimeConfigFile(file); umask(previous_umask); ASSERT_TRUE(bootstrapped) << bootstrapped.error; EXPECT_TRUE(bootstrapped.created()); struct stat metadata = {}; ASSERT_EQ(lstat(file.c_str(), &metadata), 0); EXPECT_TRUE(S_ISREG(metadata.st_mode)); EXPECT_EQ(metadata.st_mode & 0777, 0600); ASSERT_EQ(lstat(file.parent_path().c_str(), &metadata), 0); EXPECT_TRUE(S_ISDIR(metadata.st_mode)); EXPECT_EQ(metadata.st_mode & 0777, 0700); EXPECT_EQ(ReadFile(file), DefaultRuntimeConfigYaml()); const RuntimeConfigLoadResult loaded = LoadRuntimeConfig(MapEnvironment(), file); ASSERT_TRUE(loaded) << loaded.error; EXPECT_TRUE(loaded.file_loaded); EXPECT_EQ(loaded.config.graphics_backend(), GraphicsBackend::kVulkan); EXPECT_EQ(loaded.config.theme_mode(), "roblox"); EXPECT_EQ(loaded.config.frame_rate().mode, FrameRateLimitMode::kUnmanaged); EXPECT_EQ(loaded.config.vsync_mode(), "auto"); EXPECT_FALSE(loaded.config.performance().multithreaded_rendering); EXPECT_EQ(loaded.config.performance().memory_limit_mb, 0U); EXPECT_EQ(loaded.config.performance().game_mode, GameModePolicy::kAuto); EXPECT_EQ(loaded.config.performance().physics_worker_mode, PhysicsWorkerMode::kThroughput); EXPECT_EQ(loaded.config.audio_output_device(), "default"); EXPECT_EQ(loaded.config.audio_input_device(), "default"); EXPECT_FALSE(loaded.config.input_capabilities().touch_enabled); EXPECT_TRUE(loaded.config.desktop_playability()); EXPECT_EQ(loaded.config.device_profile().name, "pc-windows-11"); } TEST(RuntimeConfigBootstrapTest, MatchesShippedExampleAndDocumentsEveryDefault) { const std::filesystem::path example = std::filesystem::path(MOCKTAIL_TEST_SOURCE_DIR) / "config/mocktail.example.yaml"; const std::string defaults(DefaultRuntimeConfigYaml()); EXPECT_EQ(ReadFile(example), defaults); for (const std::string_view documented_setting : { "# Integer: configuration schema version. Only version 1 is " "supported.\nversion: 1", "# Presets: pc-windows-11, mobile-pixel-7, " "console-ps5.\n# Use the mapping below for a custom " "profile.\ndevice: pc-windows-11", "# Boolean (default: false): start without a visible SDL " "window.\n headless: false", "# String (default: roblox): use Roblox's saved account theme. " "Supported\n # overrides: dark, light, or system (follow the " "desktop color scheme).\n theme: roblox", "# Recommended values: direct-vulkan, opengl, system, " "angle-vulkan.\n backend: direct-vulkan", "# DFIntTaskSchedulerTargetFps without a whitelist.\n " "# frame_rate_limit: -1", "# Optional presentation synchronization override: auto, on, or " "off. auto\n # presents through the lowest-latency synchronized " "mode the driver offers\n # unless frame_rate_limit is unlimited.\n" " # vsync: off", "# physical CPU core. A place's Lua/main thread can still remain " "serial.\n multithreaded_rendering: false", "# sizes and coalesces midphase work. Supported: auto, latency, " "throughput.\n physics_worker_mode: throughput", "# Integer MiB (default: 0): hard RAM cap for the Mocktail process " "tree.\n # 0 disables the cap.", "# String (default: auto): request Feral GameMode when its host " "daemon and\n # client library are available. Supported values: " "auto, on, off.\n gamemode: auto", "# To pin output, copy an exact SDL device name printed during " "startup.\n output_device: default", "# between boots; prefer the exact device name when it is unique.\n" " input_device: default", "# Boolean (default: true): publish Nightcap activity to Discord " "Desktop.\n # This never signs in to Discord and never reads an " "account token.\n enabled: true", "# Boolean (default: true): show the current Roblox experience " "name.\n show_place_name: true", "# Boolean (default: true): show how long the current session has " "run.\n show_elapsed_time: true", "# Boolean (default: true): let friends open the current " "experience. When\n # Roblox provides a public server ID, the " "button targets that server.\n enabled: true", "# Boolean (default: true): never expose private or reserved " "joins.\n public_servers_only: true", "# playing: \"Playing {place_name}\"\n # state: \"by " "{creator_name}\"", "# title: Roblox", "# large: \"{place_icon}\"\n # large_text: \"{place_name}\"\n" " # small: roblox_small\n # small_text: Roblox", "# Integer (default: 1280): initial window width in logical desktop " "units.\n " "width: 1280", "# Integer (default: 720): initial window height in logical desktop " "units.\n " "height: 720", "# String (default: Roblox): window title.\n title: Roblox", "# Boolean (default: false): render at physical display-pixel density instead\n " "# of the logical desktop resolution. Enable only for sharper high-DPI output.\n " "high_dpi: false", "# HostAbi profile, run two isolated canaries with the selected " "graphics\n " "# backend, and promote only on success. An existing current " "payload is\n " "# preserved when probation fails.\n " "automatic: true", "# String (default: apk-pure): direct x86_64 APK provider " "selection. Both\n " "# accepted values resolve the provider chain, which currently " "starts with\n " "# APKPure. Supported values: auto, apk-pure.\n " "source: apk-pure", "# Reserved for desktop update integrations. `mocktail_updater` " "itself never\n # launches another process after changing the " "active payload.\n launch_after_update: false", }) { EXPECT_NE(defaults.find(documented_setting), std::string::npos) << documented_setting; } EXPECT_EQ(defaults.find("testing_latest_only"), std::string::npos); } TEST(RuntimeConfigBootstrapTest, PreservesExistingRegularFile) { TemporaryDirectory temporary; const std::filesystem::path regular = temporary.Write("user: settings\n"); RuntimeConfigBootstrapResult bootstrapped = EnsureRuntimeConfigFile(regular); ASSERT_TRUE(bootstrapped) << bootstrapped.error; EXPECT_FALSE(bootstrapped.created()); EXPECT_EQ(ReadFile(regular), "user: settings\n"); } TEST(RuntimeConfigBootstrapTest, RejectsSymlinkAndNonRegularEntry) { TemporaryDirectory temporary; const std::filesystem::path target = temporary.path() / "owned.yaml"; std::ofstream(target) << "owned: true\n"; const std::filesystem::path symlink = temporary.path() / "linked.yaml"; ASSERT_EQ(::symlink(target.c_str(), symlink.c_str()), 0); RuntimeConfigBootstrapResult bootstrapped = EnsureRuntimeConfigFile(symlink); EXPECT_FALSE(bootstrapped); EXPECT_NE(bootstrapped.error.find("symlink"), std::string::npos); EXPECT_TRUE( std::filesystem::is_symlink(std::filesystem::symlink_status(symlink))); EXPECT_EQ(ReadFile(target), "owned: true\n"); const std::filesystem::path directory = temporary.path() / "directory.yaml"; ASSERT_TRUE(std::filesystem::create_directory(directory)); bootstrapped = EnsureRuntimeConfigFile(directory); EXPECT_FALSE(bootstrapped); EXPECT_NE(bootstrapped.error.find("regular file"), std::string::npos); EXPECT_TRUE(std::filesystem::is_directory(directory)); } TEST(RuntimeConfigBootstrapTest, PublishesOnlyOneWinnerDuringFirstRunRace) { TemporaryDirectory temporary; const std::filesystem::path file = temporary.path() / "race/config.yaml"; std::vector results(12); std::vector workers; workers.reserve(results.size()); for (std::size_t index = 0; index < results.size(); ++index) { workers.emplace_back([&file, &results, index]() { results[index] = EnsureRuntimeConfigFile(file); }); } for (std::thread& worker : workers) { worker.join(); } std::size_t created = 0; for (const RuntimeConfigBootstrapResult& result : results) { ASSERT_TRUE(result) << result.error; created += result.created() ? 1 : 0; } EXPECT_EQ(created, 1); EXPECT_EQ(ReadFile(file), DefaultRuntimeConfigYaml()); } TEST(RuntimeConfigFileTest, UsesDefaultsWhenFileDoesNotExist) { const MapEnvironment environment; const RuntimeConfigLoadResult loaded = LoadRuntimeConfig(environment, "/does/not/exist/config.yaml"); ASSERT_TRUE(loaded) << loaded.error; EXPECT_FALSE(loaded.file_loaded); EXPECT_EQ(loaded.config.graphics_backend(), GraphicsBackend::kVulkan); EXPECT_EQ(loaded.config.theme_mode(), "roblox"); EXPECT_EQ(loaded.config.roblox_library_path(), "rbx_bin/libroblox.so"); } TEST(RuntimeConfigFileTest, RejectsSymlinkAndOversizedConfiguration) { TemporaryDirectory temporary; const std::filesystem::path target = temporary.Write("version: 1\n"); const std::filesystem::path symlink = temporary.path() / "linked.yaml"; ASSERT_EQ(::symlink(target.c_str(), symlink.c_str()), 0); RuntimeConfigLoadResult loaded = LoadRuntimeConfig(MapEnvironment(), symlink); EXPECT_FALSE(loaded); EXPECT_NE(loaded.error.find("cannot open"), std::string::npos); const std::filesystem::path oversized = temporary.path() / "large.yaml"; std::ofstream output(oversized, std::ios::binary); output.seekp(1024 * 1024); output.put('\n'); output.close(); loaded = LoadRuntimeConfig(MapEnvironment(), oversized); EXPECT_FALSE(loaded); EXPECT_NE(loaded.error.find("no larger than 1 MiB"), std::string::npos); } TEST(RuntimeConfigFileTest, RawMouseDefaultsOffAndLoadsFromYaml) { TemporaryDirectory defaulted_directory; const RuntimeConfigLoadResult defaulted = LoadRuntimeConfig( MapEnvironment(), defaulted_directory.Write(R"yaml( version: 1 input: touch_enabled: false )yaml")); ASSERT_TRUE(defaulted) << defaulted.error; EXPECT_FALSE(defaulted.config.input_capabilities().raw_mouse); TemporaryDirectory enabled_directory; const RuntimeConfigLoadResult enabled = LoadRuntimeConfig( MapEnvironment(), enabled_directory.Write(R"yaml( version: 1 input: raw_mouse: true )yaml")); ASSERT_TRUE(enabled) << enabled.error; EXPECT_TRUE(enabled.config.input_capabilities().raw_mouse); } TEST(RuntimeConfigFileTest, LoadsTypedDesktopAndGraphicsSettings) { TemporaryDirectory temporary; const std::filesystem::path file = temporary.Write(R"yaml( version: 1 runtime: headless: false roblox_library: /payload/libroblox.so appearance: theme: dark graphics: backend: vulkan frame_rate_limit: unlimited vsync: off performance: multithreaded_rendering: true physics_worker_mode: latency memory_limit_mb: 6144 gamemode: off audio: output_device: Built-in Audio Analog Stereo window: width: 1920 height: 1080 title: Mocktail Desktop high_dpi: true input: touch_enabled: false network: proxy_host: proxy.example.test proxy_port: 3128 integrations: discord_rpc: enabled: true show_place_name: false show_elapsed_time: false application_id: 123456789012345678 join: enabled: true public_servers_only: true button_label: Play Together text: browsing: Looking for games joining: Connecting playing: "Inside {place_name}" state: Playing Roblox on Linux unknown_place: Unknown world title: "{place_name}" images: large: "" small: nightcap_logo small_text: On Linux updates: automatic: true )yaml"); const MapEnvironment environment; const RuntimeConfigLoadResult loaded = LoadRuntimeConfig(environment, file); ASSERT_TRUE(loaded) << loaded.error; EXPECT_TRUE(loaded.file_loaded); EXPECT_EQ(loaded.config.roblox_library_path(), "/payload/libroblox.so"); EXPECT_EQ(loaded.config.graphics_backend(), GraphicsBackend::kVulkan); EXPECT_EQ(loaded.config.frame_rate().mode, FrameRateLimitMode::kUnlimited); EXPECT_EQ(loaded.config.vsync_mode(), "off"); EXPECT_EQ(loaded.config.theme_mode(), "dark"); EXPECT_TRUE(loaded.config.performance().multithreaded_rendering); EXPECT_EQ(loaded.config.performance().physics_worker_mode, PhysicsWorkerMode::kLatency); EXPECT_GT(loaded.config.performance().physical_core_count, 0); EXPECT_EQ(loaded.config.performance().memory_limit_mb, 6144U); EXPECT_EQ(loaded.config.performance().game_mode, GameModePolicy::kOff); EXPECT_EQ(loaded.config.audio_output_device(), "Built-in Audio Analog Stereo"); EXPECT_EQ(loaded.config.window().width, 1920); EXPECT_EQ(loaded.config.window().height, 1080); EXPECT_EQ(loaded.config.window().title, "Mocktail Desktop"); EXPECT_TRUE(loaded.config.window().high_dpi); EXPECT_FALSE(loaded.config.input_capabilities().touch_enabled); EXPECT_TRUE(loaded.config.desktop_playability()); ASSERT_TRUE(loaded.config.roblox_http_user_agent().has_value()); EXPECT_EQ(*loaded.config.roblox_http_user_agent(), kRobloxDesktopHttpUserAgent); ASSERT_TRUE(loaded.config.network_proxy().has_value()); EXPECT_EQ(loaded.config.network_proxy()->host, "proxy.example.test"); EXPECT_EQ(loaded.config.network_proxy()->port, 3128); EXPECT_TRUE(loaded.config.discord_rpc().enabled); EXPECT_FALSE(loaded.config.discord_rpc().show_place_name); EXPECT_FALSE(loaded.config.discord_rpc().show_elapsed_time); EXPECT_EQ(loaded.config.discord_rpc().application_id, "123456789012345678"); EXPECT_TRUE(loaded.config.discord_rpc().join_enabled); EXPECT_TRUE(loaded.config.discord_rpc().public_servers_only); EXPECT_EQ(loaded.config.discord_rpc().join_button_label, "Play Together"); EXPECT_EQ(loaded.config.discord_rpc().text.browsing, "Looking for games"); EXPECT_EQ(loaded.config.discord_rpc().text.joining, "Connecting"); EXPECT_EQ(loaded.config.discord_rpc().text.playing, "Inside {place_name}"); EXPECT_EQ(loaded.config.discord_rpc().text.state, "Playing Roblox on Linux"); EXPECT_EQ(loaded.config.discord_rpc().text.unknown_place, "Unknown world"); EXPECT_EQ(loaded.config.discord_rpc().text.title, "{place_name}"); EXPECT_TRUE(loaded.config.discord_rpc().images.large.empty()); EXPECT_EQ(loaded.config.discord_rpc().images.large_text, "{place_name}"); EXPECT_EQ(loaded.config.discord_rpc().images.small, "nightcap_logo"); EXPECT_EQ(loaded.config.discord_rpc().images.small_text, "On Linux"); EXPECT_TRUE(loaded.config.discord_rpc_valid()); } TEST(RuntimeConfigFileTest, RejectsControlBytesInDiscordPresenceFields) { TemporaryDirectory temporary; const std::filesystem::path file = temporary.Write(R"yaml( version: 1 integrations: discord_rpc: images: small_text: "bad\x01text" )yaml"); const RuntimeConfigLoadResult loaded = LoadRuntimeConfig(MapEnvironment(), file); EXPECT_FALSE(loaded); EXPECT_NE(loaded.error.find("integrations.discord_rpc.images.small_text"), std::string::npos); EXPECT_NE(loaded.error.find("control bytes"), std::string::npos) << loaded.error; } TEST(RuntimeConfigFileTest, EmptyDiscordStateLoadsAsHidden) { TemporaryDirectory temporary; const std::filesystem::path file = temporary.Write(R"yaml( version: 1 integrations: discord_rpc: text: state: "" )yaml"); const RuntimeConfigLoadResult loaded = LoadRuntimeConfig(MapEnvironment(), file); ASSERT_TRUE(loaded) << loaded.error; EXPECT_TRUE(loaded.config.discord_rpc().text.state.empty()); EXPECT_TRUE(loaded.config.discord_rpc_valid()); } TEST(RuntimeConfigFileTest, ExportsDiscordPresenceFieldsIncludingEmptyValues) { std::string error; const RuntimeConfig configured = RuntimeConfig::FromEnvironment( MapEnvironment({{"MOCKTAIL_DISCORD_RPC_TEXT_TITLE", "Nightcap"}, {"MOCKTAIL_DISCORD_RPC_TEXT_STATE", ""}, {"MOCKTAIL_DISCORD_RPC_IMAGE_LARGE", ""}, {"MOCKTAIL_DISCORD_RPC_IMAGE_SMALL", "linux"}})); ASSERT_TRUE(ExportRuntimeConfigEnvironment(configured, &error)) << error; const RuntimeConfig exported = RuntimeConfig::FromEnvironment(ProcessEnvironment()); EXPECT_EQ(exported.discord_rpc().text.title, "Nightcap"); EXPECT_TRUE(exported.discord_rpc().text.state.empty()); EXPECT_TRUE(exported.discord_rpc_valid()); EXPECT_TRUE(exported.discord_rpc().images.large.empty()); EXPECT_EQ(exported.discord_rpc().images.large_text, "{place_name}"); EXPECT_EQ(exported.discord_rpc().images.small, "linux"); EXPECT_EQ(exported.discord_rpc().images.small_text, "Roblox"); for (const char* variable : {"MOCKTAIL_DISCORD_RPC_TEXT_TITLE", "MOCKTAIL_DISCORD_RPC_TEXT_STATE", "MOCKTAIL_DISCORD_RPC_IMAGE_LARGE", "MOCKTAIL_DISCORD_RPC_IMAGE_LARGE_TEXT", "MOCKTAIL_DISCORD_RPC_IMAGE_SMALL", "MOCKTAIL_DISCORD_RPC_IMAGE_SMALL_TEXT"}) { unsetenv(variable); } } TEST(RuntimeConfigFileTest, EnvironmentOverridesYaml) { TemporaryDirectory temporary; const std::filesystem::path file = temporary.Write(R"yaml( version: 1 graphics: backend: system frame_rate_limit: 60 vsync: on performance: multithreaded_rendering: true physics_worker_mode: latency memory_limit_mb: 4096 gamemode: on audio: output_device: YAML Speakers input_device: YAML Microphone input: touch_enabled: true compatibility: desktop_playability: false window: high_dpi: true network: proxy_host: yaml-proxy.example.test proxy_port: 8080 )yaml"); const MapEnvironment environment({ {"MOCKTAIL_GRAPHICS_BACKEND", "vulkan"}, {"MOCKTAIL_FRAME_RATE_LIMIT", "144"}, {"MOCKTAIL_VSYNC", "off"}, {"MOCKTAIL_MULTITHREADED_RENDERING", "0"}, {"MOCKTAIL_PHYSICS_WORKER_MODE", "auto"}, {"MOCKTAIL_MEMORY_LIMIT_MB", "8192"}, {"MOCKTAIL_GAMEMODE", "off"}, {"MOCKTAIL_AUDIO_OUTPUT_DEVICE", "Environment Headset"}, {"MOCKTAIL_AUDIO_INPUT_DEVICE", "Environment Microphone"}, {"MOCKTAIL_TOUCH_MODE", "off"}, {"MOCKTAIL_DESKTOP_PLAYABILITY", "1"}, {"MOCKTAIL_WIN_HIGH_DPI", "0"}, {"MOCKTAIL_HTTP_PROXY_HOST", "env-proxy.example.test"}, {"MOCKTAIL_HTTP_PROXY_PORT", "1080"}, }); const RuntimeConfigLoadResult loaded = LoadRuntimeConfig(environment, file); ASSERT_TRUE(loaded) << loaded.error; EXPECT_EQ(loaded.config.graphics_backend(), GraphicsBackend::kVulkan); EXPECT_EQ(loaded.config.frame_rate().fixed_fps, 144); EXPECT_EQ(loaded.config.vsync_mode(), "off"); EXPECT_FALSE(loaded.config.performance().multithreaded_rendering); EXPECT_EQ(loaded.config.performance().physics_worker_mode, PhysicsWorkerMode::kAuto); EXPECT_EQ(loaded.config.performance().memory_limit_mb, 8192U); EXPECT_EQ(loaded.config.performance().game_mode, GameModePolicy::kOff); EXPECT_EQ(loaded.config.audio_output_device(), "Environment Headset"); EXPECT_EQ(loaded.config.audio_input_device(), "Environment Microphone"); EXPECT_FALSE(loaded.config.input_capabilities().touch_enabled); EXPECT_FALSE(loaded.config.window().high_dpi); EXPECT_TRUE(loaded.config.desktop_playability()); ASSERT_TRUE(loaded.config.roblox_http_user_agent().has_value()); EXPECT_EQ(*loaded.config.roblox_http_user_agent(), kRobloxDesktopHttpUserAgent); ASSERT_TRUE(loaded.config.network_proxy().has_value()); EXPECT_EQ(loaded.config.network_proxy()->host, "env-proxy.example.test"); EXPECT_EQ(loaded.config.network_proxy()->port, 1080); } TEST(RuntimeConfigFileTest, RejectsUnsafeAudioOutputDevice) { TemporaryDirectory temporary; const std::filesystem::path empty = temporary.Write(R"yaml( version: 1 audio: output_device: "" )yaml"); RuntimeConfigLoadResult loaded = LoadRuntimeConfig(MapEnvironment(), empty); EXPECT_FALSE(loaded); EXPECT_NE(loaded.error.find("audio.output_device"), std::string::npos); const RuntimeConfig from_environment = RuntimeConfig::FromEnvironment( MapEnvironment({{"MOCKTAIL_AUDIO_OUTPUT_DEVICE", "Speaker\nInjected"}})); EXPECT_FALSE(from_environment.audio_output_device_valid()); std::string error; EXPECT_FALSE(ExportRuntimeConfigEnvironment(from_environment, &error)); EXPECT_NE(error.find("invalid audio output device"), std::string::npos); } TEST(RuntimeConfigFileTest, RejectsInvalidHighDpiPolicy) { TemporaryDirectory temporary; const std::filesystem::path file = temporary.Write(R"yaml( version: 1 window: high_dpi: yes )yaml"); const RuntimeConfigLoadResult loaded = LoadRuntimeConfig(MapEnvironment(), file); EXPECT_FALSE(loaded); EXPECT_NE(loaded.error.find("window.high_dpi"), std::string::npos); } TEST(RuntimeConfigFileTest, RejectsUnsafeAudioInputDevice) { TemporaryDirectory temporary; const std::filesystem::path empty = temporary.Write(R"yaml( version: 1 audio: input_device: "" )yaml"); RuntimeConfigLoadResult loaded = LoadRuntimeConfig(MapEnvironment(), empty); EXPECT_FALSE(loaded); EXPECT_NE(loaded.error.find("audio.input_device"), std::string::npos); const RuntimeConfig from_environment = RuntimeConfig::FromEnvironment( MapEnvironment({{"MOCKTAIL_AUDIO_INPUT_DEVICE", "Mic\nInjected"}})); EXPECT_FALSE(from_environment.audio_input_device_valid()); std::string error; EXPECT_FALSE(ExportRuntimeConfigEnvironment(from_environment, &error)); EXPECT_NE(error.find("invalid audio input device"), std::string::npos); } TEST(RuntimeConfigFileTest, RejectsIncompleteOrInvalidNetworkProxy) { TemporaryDirectory temporary; const std::filesystem::path incomplete = temporary.Write(R"yaml( version: 1 network: proxy_host: proxy.example.test )yaml"); RuntimeConfigLoadResult loaded = LoadRuntimeConfig(MapEnvironment(), incomplete); EXPECT_FALSE(loaded); EXPECT_NE(loaded.error.find("configured together"), std::string::npos); const std::filesystem::path scheme = temporary.Write(R"yaml( version: 1 network: proxy_host: http://proxy.example.test proxy_port: 8080 )yaml"); loaded = LoadRuntimeConfig(MapEnvironment(), scheme); EXPECT_FALSE(loaded); EXPECT_NE(loaded.error.find("without a scheme"), std::string::npos); const std::filesystem::path path = temporary.Write(R"yaml( version: 1 network: proxy_host: proxy.example.test/path proxy_port: 8080 )yaml"); loaded = LoadRuntimeConfig(MapEnvironment(), path); EXPECT_FALSE(loaded); const std::filesystem::path port = temporary.Write(R"yaml( version: 1 network: proxy_host: proxy.example.test proxy_port: 65536 )yaml"); loaded = LoadRuntimeConfig(MapEnvironment(), port); EXPECT_FALSE(loaded); EXPECT_NE(loaded.error.find("1 to 65535"), std::string::npos); } TEST(RuntimeConfigFileTest, LoadsSystemProxyFlagWithoutFixedEndpoint) { TemporaryDirectory temporary; const std::filesystem::path path = temporary.Write(R"yaml( version: 1 network: use_system_proxy: true )yaml"); const RuntimeConfigLoadResult loaded = LoadRuntimeConfig(MapEnvironment(), path); ASSERT_TRUE(loaded) << loaded.error; EXPECT_TRUE(loaded.config.use_system_proxy()); EXPECT_FALSE(loaded.config.network_proxy().has_value()); } TEST(RuntimeConfigFileTest, ExportsProxyVariablesOnlyWhenConfigured) { unsetenv("MOCKTAIL_HTTP_PROXY_HOST"); unsetenv("MOCKTAIL_HTTP_PROXY_PORT"); unsetenv("MOCKTAIL_HTTP_PROXY_SCHEME"); unsetenv("MOCKTAIL_NATIVE_SET_HTTP_CLIENT_PROXY"); std::string error; ASSERT_TRUE(ExportRuntimeConfigEnvironment( RuntimeConfig::FromEnvironment(MapEnvironment()), &error)) << error; EXPECT_EQ(getenv("MOCKTAIL_HTTP_PROXY_HOST"), nullptr); EXPECT_EQ(getenv("MOCKTAIL_HTTP_PROXY_PORT"), nullptr); EXPECT_EQ(getenv("MOCKTAIL_NATIVE_SET_HTTP_CLIENT_PROXY"), nullptr); const RuntimeConfig configured = RuntimeConfig::FromEnvironment( MapEnvironment({{"MOCKTAIL_HTTP_PROXY_HOST", "127.0.0.1"}, {"MOCKTAIL_HTTP_PROXY_PORT", "7890"}})); ASSERT_TRUE(ExportRuntimeConfigEnvironment(configured, &error)) << error; ASSERT_NE(getenv("MOCKTAIL_HTTP_PROXY_HOST"), nullptr); ASSERT_NE(getenv("MOCKTAIL_HTTP_PROXY_PORT"), nullptr); ASSERT_NE(getenv("MOCKTAIL_HTTP_PROXY_SCHEME"), nullptr); ASSERT_NE(getenv("MOCKTAIL_NATIVE_SET_HTTP_CLIENT_PROXY"), nullptr); EXPECT_STREQ(getenv("MOCKTAIL_HTTP_PROXY_HOST"), "127.0.0.1"); EXPECT_STREQ(getenv("MOCKTAIL_HTTP_PROXY_PORT"), "7890"); EXPECT_STREQ(getenv("MOCKTAIL_HTTP_PROXY_SCHEME"), "http"); EXPECT_STREQ(getenv("MOCKTAIL_NATIVE_SET_HTTP_CLIENT_PROXY"), "1"); unsetenv("MOCKTAIL_HTTP_PROXY_HOST"); unsetenv("MOCKTAIL_HTTP_PROXY_PORT"); unsetenv("MOCKTAIL_HTTP_PROXY_SCHEME"); unsetenv("MOCKTAIL_NATIVE_SET_HTTP_CLIENT_PROXY"); } TEST(RuntimeConfigFileTest, ExportsMultithreadedRenderingPolicy) { unsetenv("MOCKTAIL_MULTITHREADED_RENDERING"); std::string error; const RuntimeConfig enabled = RuntimeConfig::FromEnvironment( MapEnvironment({{"MOCKTAIL_MULTITHREADED_RENDERING", "1"}})); ASSERT_TRUE(ExportRuntimeConfigEnvironment(enabled, &error)) << error; ASSERT_NE(getenv("MOCKTAIL_MULTITHREADED_RENDERING"), nullptr); EXPECT_STREQ(getenv("MOCKTAIL_MULTITHREADED_RENDERING"), "1"); const RuntimeConfig disabled = RuntimeConfig::FromEnvironment(MapEnvironment()); ASSERT_TRUE(ExportRuntimeConfigEnvironment(disabled, &error)) << error; EXPECT_STREQ(getenv("MOCKTAIL_MULTITHREADED_RENDERING"), "0"); unsetenv("MOCKTAIL_MULTITHREADED_RENDERING"); } TEST(RuntimeConfigFileTest, ExportsHighDpiWindowPolicy) { unsetenv("MOCKTAIL_WIN_HIGH_DPI"); std::string error; const RuntimeConfig default_config = RuntimeConfig::FromEnvironment(MapEnvironment()); ASSERT_TRUE(ExportRuntimeConfigEnvironment(default_config, &error)) << error; ASSERT_NE(getenv("MOCKTAIL_WIN_HIGH_DPI"), nullptr); EXPECT_STREQ(getenv("MOCKTAIL_WIN_HIGH_DPI"), "0"); const RuntimeConfig high_dpi = RuntimeConfig::FromEnvironment( MapEnvironment({{"MOCKTAIL_WIN_HIGH_DPI", "1"}})); ASSERT_TRUE(ExportRuntimeConfigEnvironment(high_dpi, &error)) << error; EXPECT_STREQ(getenv("MOCKTAIL_WIN_HIGH_DPI"), "1"); unsetenv("MOCKTAIL_WIN_HIGH_DPI"); } TEST(RuntimeConfigFileTest, ExportsPhysicsWorkerMode) { unsetenv("MOCKTAIL_PHYSICS_WORKER_MODE"); std::string error; const RuntimeConfig latency = RuntimeConfig::FromEnvironment( MapEnvironment({{"MOCKTAIL_PHYSICS_WORKER_MODE", "latency"}})); ASSERT_TRUE(ExportRuntimeConfigEnvironment(latency, &error)) << error; ASSERT_NE(getenv("MOCKTAIL_PHYSICS_WORKER_MODE"), nullptr); EXPECT_STREQ(getenv("MOCKTAIL_PHYSICS_WORKER_MODE"), "latency"); const RuntimeConfig default_config = RuntimeConfig::FromEnvironment(MapEnvironment()); ASSERT_TRUE(ExportRuntimeConfigEnvironment(default_config, &error)) << error; EXPECT_STREQ(getenv("MOCKTAIL_PHYSICS_WORKER_MODE"), "throughput"); unsetenv("MOCKTAIL_PHYSICS_WORKER_MODE"); } TEST(RuntimeConfigFileTest, ValidatesAndExportsMemoryLimitPolicy) { TemporaryDirectory temporary; const std::filesystem::path negative = temporary.Write(R"yaml( version: 1 performance: memory_limit_mb: -1 )yaml"); RuntimeConfigLoadResult loaded = LoadRuntimeConfig(MapEnvironment(), negative); EXPECT_FALSE(loaded); EXPECT_NE(loaded.error.find("memory_limit_mb"), std::string::npos); loaded = LoadRuntimeConfig( MapEnvironment({{"MOCKTAIL_MEMORY_LIMIT_MB", "not-a-number"}}), "/does/not/exist/config.yaml"); EXPECT_FALSE(loaded); EXPECT_NE(loaded.error.find("memory-limit policy"), std::string::npos); unsetenv("MOCKTAIL_MEMORY_LIMIT_MB"); std::string error; const RuntimeConfig configured = RuntimeConfig::FromEnvironment( MapEnvironment({{"MOCKTAIL_MEMORY_LIMIT_MB", "6144"}})); ASSERT_TRUE(ExportRuntimeConfigEnvironment(configured, &error)) << error; ASSERT_NE(getenv("MOCKTAIL_MEMORY_LIMIT_MB"), nullptr); EXPECT_STREQ(getenv("MOCKTAIL_MEMORY_LIMIT_MB"), "6144"); unsetenv("MOCKTAIL_MEMORY_LIMIT_MB"); } TEST(RuntimeConfigFileTest, ValidatesAndExportsGameModePolicy) { TemporaryDirectory temporary; const std::filesystem::path invalid = temporary.Write(R"yaml( version: 1 performance: gamemode: required )yaml"); RuntimeConfigLoadResult loaded = LoadRuntimeConfig(MapEnvironment(), invalid); EXPECT_FALSE(loaded); EXPECT_NE(loaded.error.find("performance.gamemode"), std::string::npos); loaded = LoadRuntimeConfig(MapEnvironment({{"MOCKTAIL_GAMEMODE", "invalid"}}), "/does/not/exist/config.yaml"); EXPECT_FALSE(loaded); EXPECT_NE(loaded.error.find("GameMode policy"), std::string::npos); unsetenv("MOCKTAIL_GAMEMODE"); std::string error; const RuntimeConfig configured = RuntimeConfig::FromEnvironment( MapEnvironment({{"MOCKTAIL_GAMEMODE", "on"}})); ASSERT_TRUE(ExportRuntimeConfigEnvironment(configured, &error)) << error; ASSERT_NE(getenv("MOCKTAIL_GAMEMODE"), nullptr); EXPECT_STREQ(getenv("MOCKTAIL_GAMEMODE"), "on"); unsetenv("MOCKTAIL_GAMEMODE"); } TEST(RuntimeConfigFileTest, ExportsAudioOutputDevice) { unsetenv("MOCKTAIL_AUDIO_OUTPUT_DEVICE"); std::string error; const RuntimeConfig configured = RuntimeConfig::FromEnvironment( MapEnvironment({{"MOCKTAIL_AUDIO_OUTPUT_DEVICE", "USB Headset"}})); ASSERT_TRUE(ExportRuntimeConfigEnvironment(configured, &error)) << error; ASSERT_NE(getenv("MOCKTAIL_AUDIO_OUTPUT_DEVICE"), nullptr); EXPECT_STREQ(getenv("MOCKTAIL_AUDIO_OUTPUT_DEVICE"), "USB Headset"); unsetenv("MOCKTAIL_AUDIO_OUTPUT_DEVICE"); } TEST(RuntimeConfigFileTest, ExportsAudioInputDevice) { unsetenv("MOCKTAIL_AUDIO_INPUT_DEVICE"); std::string error; const RuntimeConfig configured = RuntimeConfig::FromEnvironment( MapEnvironment({{"MOCKTAIL_AUDIO_INPUT_DEVICE", "USB Microphone"}})); ASSERT_TRUE(ExportRuntimeConfigEnvironment(configured, &error)) << error; ASSERT_NE(getenv("MOCKTAIL_AUDIO_INPUT_DEVICE"), nullptr); EXPECT_STREQ(getenv("MOCKTAIL_AUDIO_INPUT_DEVICE"), "USB Microphone"); unsetenv("MOCKTAIL_AUDIO_INPUT_DEVICE"); } TEST(RuntimeConfigFileTest, ExportsDesktopPlayabilityUserAgent) { unsetenv("MOCKTAIL_USER_AGENT"); unsetenv("MOCKTAIL_DESKTOP_PLAYABILITY"); unsetenv("MOCKTAIL_DEVICE_PROFILE"); std::string error; const RuntimeConfig desktop = RuntimeConfig::FromEnvironment(MapEnvironment()); ASSERT_TRUE(ExportRuntimeConfigEnvironment(desktop, &error)) << error; ASSERT_NE(getenv("MOCKTAIL_DESKTOP_PLAYABILITY"), nullptr); EXPECT_STREQ(getenv("MOCKTAIL_DESKTOP_PLAYABILITY"), "1"); ASSERT_NE(getenv("MOCKTAIL_DEVICE_PROFILE"), nullptr); EXPECT_STREQ(getenv("MOCKTAIL_DEVICE_PROFILE"), "pc-windows-11"); EXPECT_STREQ(getenv("MOCKTAIL_DEVICE_CLASS"), "pc"); EXPECT_STREQ(getenv("MOCKTAIL_DEVICE_PLATFORM_NAME"), "Windows"); EXPECT_STREQ(getenv("MOCKTAIL_DEVICE_NAME"), "Windows 11 PC"); EXPECT_STREQ(getenv("MOCKTAIL_DEVICE_MANUFACTURER"), "Microsoft"); EXPECT_STREQ(getenv("MOCKTAIL_DEVICE_MODEL"), "Windows 11 PC"); ASSERT_NE(getenv("MOCKTAIL_USER_AGENT"), nullptr); EXPECT_STREQ(getenv("MOCKTAIL_USER_AGENT"), kRobloxDesktopHttpUserAgent.data()); unsetenv("MOCKTAIL_USER_AGENT"); unsetenv("MOCKTAIL_DESKTOP_PLAYABILITY"); unsetenv("MOCKTAIL_DEVICE_PROFILE"); } TEST(RuntimeConfigFileTest, LoadsOneLineDevicePresetsAndAliases) { struct ExpectedProfile { const char* configured; const char* canonical; DeviceClass device_class; const char* model; bool touch; const char* user_agent; }; for (const ExpectedProfile& expected : { ExpectedProfile{"pc-windows-11", "pc-windows-11", DeviceClass::kPc, "Windows 11 PC", false, "Roblox/WinInet"}, ExpectedProfile{"mobile", "mobile-pixel-7", DeviceClass::kMobile, "Google Pixel 7", true, nullptr}, ExpectedProfile{"console", "console-ps5", DeviceClass::kConsole, "PlayStation 5", false, "Roblox/XboxOne"}, }) { SCOPED_TRACE(expected.configured); TemporaryDirectory temporary; const std::filesystem::path file = temporary.Write( "version: 1\ndevice: " + std::string(expected.configured) + "\n"); const RuntimeConfigLoadResult loaded = LoadRuntimeConfig(MapEnvironment(), file); ASSERT_TRUE(loaded) << loaded.error; EXPECT_EQ(loaded.config.device_profile().name, expected.canonical); EXPECT_EQ(loaded.config.device_profile().device_class, expected.device_class); EXPECT_EQ(loaded.config.device_profile().display_name, expected.model); EXPECT_EQ(loaded.config.input_capabilities().touch_enabled, expected.touch); if (expected.user_agent == nullptr) { EXPECT_FALSE(loaded.config.roblox_http_user_agent().has_value()); } else { ASSERT_TRUE(loaded.config.roblox_http_user_agent().has_value()); EXPECT_EQ(*loaded.config.roblox_http_user_agent(), expected.user_agent); } } } TEST(RuntimeConfigFileTest, LoadsDetailedDeviceConfiguration) { TemporaryDirectory temporary; const std::filesystem::path file = temporary.Write(R"yaml( version: 1 device: type: mobile platform: Android name: Google Pixel 9 Pro manufacturer: Google model: Pixel 9 Pro brand: google code: komodo sku: pixel-9-pro soc_model: Google Tensor G4 touch: true mouse: true keyboard: false )yaml"); const RuntimeConfigLoadResult loaded = LoadRuntimeConfig(MapEnvironment(), file); ASSERT_TRUE(loaded) << loaded.error; EXPECT_EQ(loaded.config.device_profile().name, "mobile-pixel-7"); EXPECT_EQ(loaded.config.device_profile().platform_name, "Android"); EXPECT_EQ(loaded.config.device_profile().display_name, "Google Pixel 9 Pro"); EXPECT_EQ(loaded.config.device_profile().manufacturer, "Google"); EXPECT_EQ(loaded.config.device_profile().model, "Pixel 9 Pro"); EXPECT_EQ(loaded.config.device_profile().brand, "google"); EXPECT_EQ(loaded.config.device_profile().device_code, "komodo"); EXPECT_EQ(loaded.config.device_profile().device_sku, "pixel-9-pro"); EXPECT_EQ(loaded.config.device_profile().soc_model, "Google Tensor G4"); EXPECT_NE(loaded.config.device_profile().cache_key, loaded.config.device_profile().name); EXPECT_TRUE(loaded.config.input_capabilities().touch_enabled); EXPECT_TRUE(loaded.config.input_capabilities().mouse_enabled); EXPECT_FALSE(loaded.config.input_capabilities().keyboard_enabled); } TEST(RuntimeConfigFileTest, ExportsMobileIdentityAndClearsDesktopUserAgent) { ASSERT_EQ(0, setenv("MOCKTAIL_USER_AGENT", "stale-desktop", 1)); const RuntimeConfig mobile = RuntimeConfig::FromEnvironment( MapEnvironment({{"MOCKTAIL_DEVICE_PROFILE", "mobile-pixel-7"}})); std::string error; ASSERT_TRUE(ExportRuntimeConfigEnvironment(mobile, &error)) << error; EXPECT_EQ(getenv("MOCKTAIL_USER_AGENT"), nullptr); EXPECT_STREQ(getenv("MOCKTAIL_DEVICE_PROFILE"), "mobile-pixel-7"); EXPECT_STREQ(getenv("MOCKTAIL_DEVICE_CLASS"), "mobile"); EXPECT_STREQ(getenv("MOCKTAIL_DEVICE_PLATFORM_NAME"), "Android"); EXPECT_STREQ(getenv("MOCKTAIL_DEVICE_NAME"), "Google Pixel 7"); EXPECT_STREQ(getenv("MOCKTAIL_DEVICE_MODEL"), "Pixel 7"); EXPECT_STREQ(getenv("MOCKTAIL_DEVICE_CODE"), "panther"); EXPECT_STREQ(getenv("MOCKTAIL_DEVICE_SOC_MODEL"), "Google Tensor G2"); EXPECT_STREQ(getenv("MOCKTAIL_TOUCH_MODE"), "on"); EXPECT_STREQ(getenv("MOCKTAIL_MOUSE_MODE"), "off"); EXPECT_STREQ(getenv("MOCKTAIL_KEYBOARD_MODE"), "off"); EXPECT_STREQ(getenv("MOCKTAIL_DESKTOP_PLAYABILITY"), "0"); } TEST(RuntimeConfigFileTest, EnvironmentDevicePresetOverridesYamlPreset) { TemporaryDirectory temporary; const std::filesystem::path file = temporary.Write(R"yaml( version: 1 device: mobile-pixel-7 )yaml"); const RuntimeConfigLoadResult loaded = LoadRuntimeConfig( MapEnvironment({{"MOCKTAIL_DEVICE_PROFILE", "console"}}), file); ASSERT_TRUE(loaded) << loaded.error; EXPECT_EQ(loaded.config.device_profile().name, "console-ps5"); EXPECT_EQ(loaded.config.device_profile().device_class, DeviceClass::kConsole); ASSERT_TRUE(loaded.config.roblox_http_user_agent().has_value()); EXPECT_EQ(*loaded.config.roblox_http_user_agent(), kRobloxConsoleAdmissionUserAgent); } TEST(RuntimeConfigFileTest, RejectsUnknownOrContradictoryDevicePreset) { TemporaryDirectory temporary; const std::filesystem::path unknown = temporary.Write(R"yaml( version: 1 device: smart-fridge )yaml"); RuntimeConfigLoadResult loaded = LoadRuntimeConfig(MapEnvironment(), unknown); EXPECT_FALSE(loaded); EXPECT_NE(loaded.error.find("device must be"), std::string::npos); const std::filesystem::path contradictory = temporary.Write(R"yaml( version: 1 device: mobile-pixel-7 input: touch_enabled: false )yaml"); loaded = LoadRuntimeConfig(MapEnvironment(), contradictory); EXPECT_FALSE(loaded); EXPECT_NE(loaded.error.find("cannot be combined"), std::string::npos); } TEST(RuntimeConfigFileTest, RejectsInvalidDetailedDeviceConfiguration) { TemporaryDirectory temporary; const std::filesystem::path missing_type = temporary.Write(R"yaml( version: 1 device: model: Pixel 9 Pro )yaml"); RuntimeConfigLoadResult loaded = LoadRuntimeConfig(MapEnvironment(), missing_type); EXPECT_FALSE(loaded); EXPECT_NE(loaded.error.find("device.type is required"), std::string::npos); const std::filesystem::path empty_model = temporary.Write(R"yaml( version: 1 device: type: mobile model: "" )yaml"); loaded = LoadRuntimeConfig(MapEnvironment(), empty_model); EXPECT_FALSE(loaded); EXPECT_NE(loaded.error.find("device.model"), std::string::npos); const std::filesystem::path invalid_input = temporary.Write(R"yaml( version: 1 device: type: console touch: false mouse: false keyboard: false )yaml"); loaded = LoadRuntimeConfig(MapEnvironment(), invalid_input); EXPECT_FALSE(loaded); EXPECT_NE(loaded.error.find("at least one usable input"), std::string::npos); } TEST(RuntimeConfigFileTest, LoadsMobilePlayabilityWithoutChangingTouch) { TemporaryDirectory temporary; const std::filesystem::path file = temporary.Write(R"yaml( version: 1 input: touch_enabled: false compatibility: desktop_playability: false )yaml"); const RuntimeConfigLoadResult loaded = LoadRuntimeConfig(MapEnvironment(), file); ASSERT_TRUE(loaded) << loaded.error; EXPECT_FALSE(loaded.config.input_capabilities().touch_enabled); EXPECT_FALSE(loaded.config.desktop_playability()); EXPECT_FALSE(loaded.config.roblox_http_user_agent().has_value()); } TEST(RuntimeConfigFileTest, RejectsInvalidDesktopPlayabilityValue) { TemporaryDirectory temporary; const std::filesystem::path file = temporary.Write(R"yaml( version: 1 compatibility: desktop_playability: automatic )yaml"); const RuntimeConfigLoadResult loaded = LoadRuntimeConfig(MapEnvironment(), file); EXPECT_FALSE(loaded); EXPECT_NE(loaded.error.find("compatibility.desktop_playability"), std::string::npos); } TEST(RuntimeConfigFileTest, AcceptsProductionDirectVulkanBackendName) { MapEnvironment environment({{"MOCKTAIL_GRAPHICS_BACKEND", "direct-vulkan"}}); const RuntimeConfigLoadResult loaded = LoadRuntimeConfig(environment, "/does/not/exist/direct-vulkan.yaml"); ASSERT_TRUE(loaded) << loaded.error; EXPECT_EQ(loaded.config.graphics_backend(), GraphicsBackend::kVulkan); } TEST(RuntimeConfigFileTest, AcceptsStrictOpenGlBackendName) { MapEnvironment environment({{"MOCKTAIL_GRAPHICS_BACKEND", "opengl"}}); const RuntimeConfigLoadResult loaded = LoadRuntimeConfig(environment, "/does/not/exist/opengl.yaml"); ASSERT_TRUE(loaded) << loaded.error; EXPECT_EQ(loaded.config.graphics_backend(), GraphicsBackend::kSystem); EXPECT_EQ(loaded.config.graphics_backend_name(), "opengl"); } TEST(RuntimeConfigFileTest, LoadsStrictOpenGlFromYamlWithoutEnvironment) { TemporaryDirectory temporary; const std::filesystem::path file = temporary.Write(R"yaml( version: 1 graphics: backend: opengl )yaml"); const RuntimeConfigLoadResult loaded = LoadRuntimeConfig(MapEnvironment(), file); ASSERT_TRUE(loaded) << loaded.error; EXPECT_EQ(loaded.config.graphics_backend(), GraphicsBackend::kSystem); EXPECT_EQ(loaded.config.graphics_backend_name(), "opengl"); } TEST(RuntimeConfigFileTest, RejectsInvalidKnownSettings) { TemporaryDirectory temporary; const std::filesystem::path file = temporary.Write(R"yaml( version: 1 graphics: frame_rate_limit: fastest )yaml"); const RuntimeConfigLoadResult loaded = LoadRuntimeConfig(MapEnvironment(), file); EXPECT_FALSE(loaded); EXPECT_NE(loaded.error.find("frame_rate_limit"), std::string::npos); } TEST(RuntimeConfigFileTest, RejectsNonBooleanPerformancePolicy) { TemporaryDirectory temporary; const std::filesystem::path file = temporary.Write(R"yaml( version: 1 performance: multithreaded_rendering: automatic )yaml"); const RuntimeConfigLoadResult loaded = LoadRuntimeConfig(MapEnvironment(), file); EXPECT_FALSE(loaded); EXPECT_NE(loaded.error.find("performance.multithreaded_rendering"), std::string::npos); } TEST(RuntimeConfigFileTest, RejectsUnknownPhysicsWorkerMode) { TemporaryDirectory temporary; const std::filesystem::path file = temporary.Write(R"yaml( version: 1 performance: physics_worker_mode: maximum )yaml"); const RuntimeConfigLoadResult loaded = LoadRuntimeConfig(MapEnvironment(), file); EXPECT_FALSE(loaded); EXPECT_NE(loaded.error.find("performance.physics_worker_mode"), std::string::npos); } TEST(RuntimeConfigFileTest, RejectsMalformedYaml) { TemporaryDirectory temporary; const std::filesystem::path file = temporary.Write("graphics: [\n"); const RuntimeConfigLoadResult loaded = LoadRuntimeConfig(MapEnvironment(), file); EXPECT_FALSE(loaded); EXPECT_NE(loaded.error.find("invalid YAML"), std::string::npos); } TEST(RuntimeConfigFileTest, FleasionRoutesThroughConfiguredLoopbackPort) { TemporaryDirectory temporary; const auto file = temporary.Write( "integrations:\n fleasion:\n enabled: true\n proxy_mode: env\n" " proxy_port: 59443\n ca_certificate: /tmp/fleasion-ca.crt\n"); auto loaded = LoadRuntimeConfig(MapEnvironment(), file); ASSERT_TRUE(loaded) << loaded.error; EXPECT_TRUE(loaded.config.fleasion_enabled()); ASSERT_TRUE(loaded.config.network_proxy()); EXPECT_EQ(loaded.config.network_proxy()->host, "127.0.0.1"); EXPECT_EQ(loaded.config.network_proxy()->port, 59443); EXPECT_EQ(*loaded.config.fleasion_ca_certificate(), "/tmp/fleasion-ca.crt"); loaded = LoadRuntimeConfig(MapEnvironment({{"MOCKTAIL_FLEASION_ENABLED", "0"}}), file); ASSERT_TRUE(loaded) << loaded.error; EXPECT_FALSE(loaded.config.fleasion_enabled()); EXPECT_FALSE(loaded.config.network_proxy()); } TEST(RuntimeConfigFileTest, FleasionRejectsInvalidConfigurationAndProxyConflicts) { TemporaryDirectory temporary; for (const char* setting : {"proxy_port: 0", "proxy_port: 65536", "proxy_port: 123x", "proxy_mode: invalid", "ca_certificate: relative.pem"}) { const auto file = temporary.Write(std::string( "integrations:\n fleasion:\n enabled: true\n ") + setting + "\n"); EXPECT_FALSE(LoadRuntimeConfig(MapEnvironment(), file)) << setting; } const auto file = temporary.Write("integrations:\n fleasion:\n enabled: true\n"); EXPECT_FALSE(LoadRuntimeConfig(MapEnvironment({{"MOCKTAIL_USE_SYSTEM_PROXY", "1"}}), file)); EXPECT_FALSE(LoadRuntimeConfig(MapEnvironment({{"MOCKTAIL_HTTP_PROXY_HOST", "other"}, {"MOCKTAIL_HTTP_PROXY_PORT", "3128"}}), file)); } TEST(RuntimeConfigFileTest, FleasionEnvironmentRoundTripsAndHostsModeDoesNotSetProxy) { TemporaryDirectory temporary; const auto file = temporary.Write("integrations:\n fleasion:\n enabled: true\n" " proxy_mode: hosts\n"); const auto loaded = LoadRuntimeConfig(MapEnvironment(), file); ASSERT_TRUE(loaded) << loaded.error; EXPECT_FALSE(loaded.config.network_proxy()); std::string error; ASSERT_TRUE(ExportRuntimeConfigEnvironment(loaded.config, &error)) << error; const auto resolved = RuntimeConfig::FromEnvironment(ProcessEnvironment()); EXPECT_TRUE(resolved.fleasion_enabled()); EXPECT_TRUE(resolved.fleasion_valid()); EXPECT_EQ(resolved.fleasion_proxy_mode(), "hosts"); ASSERT_TRUE(ExportRuntimeConfigEnvironment(RuntimeConfig::FromEnvironment(MapEnvironment()), &error)); } } // namespace } // namespace runtime } // namespace mocktail