Files
dim-ghub 1854e00934 feat(vr): add SMAA as a vr.anti_aliasing choice
vr.anti_aliasing now takes fxaa, smaa or off. SMAA runs the reference
implementation (third_party/smaa, MIT) at its High preset: luma edge
detection and blending weights into per-eye images, then neighborhood
blending into the runtime's swapchain through FXAA's final pass. Its area and
search lookup textures are uploaded once per device. If SMAA cannot be set
up, the eyes fall back to FXAA.

Also export vr.anti_aliasing from config.yaml: the VR backend reads the
process environment, so the setting only worked as an environment variable.

On a Quest 2 through SteamVR at 120 Hz (2136x2236 per eye), SMAA ran at 118.6
fps against 118.8 for FXAA, with p99 frame times of 10.1 and 9.5 ms.
2026-10-16 01:12:21 -05:00

1365 lines
53 KiB
C++

// Modified by vii from komaruworld/mocktail. See README "About this fork".
#include "runtime/runtime_config_file.h"
#include <gtest/gtest.h>
#include <sys/stat.h>
#include <unistd.h>
#include <filesystem>
#include <fstream>
#include <optional>
#include <string>
#include <string_view>
#include <thread>
#include <unordered_map>
#include <utility>
#include <vector>
#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<std::string, std::string> values = {})
: values_(std::move(values)) {}
std::optional<std::string> Get(std::string_view name) const override {
const auto found = values_.find(std::string(name));
return found == values_.end() ? std::nullopt
: std::optional<std::string>(found->second);
}
private:
std::unordered_map<std::string, std::string> 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<char>(input),
std::istreambuf_iterator<char>());
}
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.vr_enabled(), kDefaultVrEnabled);
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(RuntimeConfigFileTest,
VrEnvironmentOverridesYamlIncludingExplicitDisable) {
TemporaryDirectory temporary;
const auto file = temporary.Write("version: 1\nvr:\n enabled: true\n");
const auto enabled = LoadRuntimeConfig(MapEnvironment(), file);
ASSERT_TRUE(enabled) << enabled.error;
EXPECT_TRUE(enabled.config.vr_enabled());
const auto disabled =
LoadRuntimeConfig(MapEnvironment({{"MOCKTAIL_VR_ENABLED", "0"}}), file);
ASSERT_TRUE(disabled) << disabled.error;
EXPECT_FALSE(disabled.config.vr_enabled());
const auto invalid = LoadRuntimeConfig(
MapEnvironment({{"MOCKTAIL_VR_ENABLED", "maybe"}}), file);
EXPECT_FALSE(invalid);
EXPECT_NE(invalid.error.find("VR configuration"), std::string::npos);
}
TEST(RuntimeConfigFileTest, VrModeSelectsOnOffOrAuto) {
TemporaryDirectory temporary;
const auto load = [&](const std::string& vr, MapEnvironment environment =
MapEnvironment()) {
return LoadRuntimeConfig(environment,
temporary.Write("version: 1\nvr:\n" + vr));
};
const auto on = load(" mode: on\n");
ASSERT_TRUE(on) << on.error;
EXPECT_EQ(on.config.vr_mode(), VrMode::kOn);
EXPECT_TRUE(on.config.vr_enabled());
const auto off = load(" mode: off\n");
ASSERT_TRUE(off) << off.error;
EXPECT_EQ(off.config.vr_mode(), VrMode::kOff);
EXPECT_FALSE(off.config.vr_enabled());
// auto stays on the desktop until main() finds a headset.
const auto automatic = load(" mode: auto\n");
ASSERT_TRUE(automatic) << automatic.error;
EXPECT_EQ(automatic.config.vr_mode(), VrMode::kAuto);
EXPECT_FALSE(automatic.config.vr_enabled());
// The older key still means on/off.
EXPECT_EQ(load(" enabled: true\n").config.vr_mode(), VrMode::kOn);
EXPECT_EQ(load(" enabled: false\n").config.vr_mode(), VrMode::kOff);
// --vr / --no-vr (and a detected headset) win over the file.
const auto forced = load(" mode: auto\n",
MapEnvironment({{"MOCKTAIL_VR_ENABLED", "1"}}));
ASSERT_TRUE(forced) << forced.error;
EXPECT_EQ(forced.config.vr_mode(), VrMode::kOn);
EXPECT_TRUE(forced.config.vr_enabled());
const auto desktop = load(" mode: on\n",
MapEnvironment({{"MOCKTAIL_VR_ENABLED", "0"}}));
ASSERT_TRUE(desktop) << desktop.error;
EXPECT_FALSE(desktop.config.vr_enabled());
const auto both = load(" mode: auto\n enabled: true\n");
EXPECT_FALSE(both);
EXPECT_NE(both.error.find("vr.mode"), std::string::npos);
const auto invalid = load(" mode: sometimes\n");
EXPECT_FALSE(invalid);
EXPECT_NE(invalid.error.find("vr.mode"), std::string::npos);
}
TEST(RuntimeConfigFileTest, VrAntiAliasingAcceptsFxaaSmaaOrOff) {
TemporaryDirectory temporary;
for (const char* value : {"fxaa", "smaa", "off"}) {
const auto loaded = LoadRuntimeConfig(
MapEnvironment(),
temporary.Write(std::string("version: 1\nvr:\n anti_aliasing: ") +
value + "\n"));
EXPECT_TRUE(loaded) << value << ": " << loaded.error;
}
// The VR backend reads the exported variable, not the file.
const char* previous = std::getenv("MOCKTAIL_VR_ANTI_ALIASING");
const std::optional<std::string> saved =
previous ? std::optional<std::string>(previous) : std::nullopt;
const auto smaa = LoadRuntimeConfig(
MapEnvironment(),
temporary.Write("version: 1\nvr:\n anti_aliasing: smaa\n"));
ASSERT_TRUE(smaa) << smaa.error;
EXPECT_EQ(smaa.config.vr_anti_aliasing(), "smaa");
std::string error;
EXPECT_TRUE(ExportRuntimeConfigEnvironment(smaa.config, &error)) << error;
EXPECT_STREQ(std::getenv("MOCKTAIL_VR_ANTI_ALIASING"), "smaa");
if (saved)
setenv("MOCKTAIL_VR_ANTI_ALIASING", saved->c_str(), 1);
else
unsetenv("MOCKTAIL_VR_ANTI_ALIASING");
const auto invalid = LoadRuntimeConfig(
MapEnvironment(),
temporary.Write("version: 1\nvr:\n anti_aliasing: msaa\n"));
EXPECT_FALSE(invalid);
EXPECT_NE(invalid.error.find("vr.anti_aliasing"), std::string::npos);
}
TEST(RuntimeConfigFileTest, VrFrameRateLimitIsSeparateFromDesktopLimit) {
TemporaryDirectory temporary;
const auto defaults = LoadRuntimeConfig(
MapEnvironment({{"MOCKTAIL_VR_ENABLED", "1"}}),
temporary.Write("version: 1\n"));
ASSERT_TRUE(defaults) << defaults.error;
EXPECT_EQ(defaults.config.frame_rate().mode, FrameRateLimitMode::kUnmanaged);
EXPECT_EQ(defaults.config.vr_frame_rate().mode,
FrameRateLimitMode::kUnlimited);
EXPECT_EQ(defaults.config.active_frame_rate().mode,
FrameRateLimitMode::kUnlimited);
const auto file = temporary.Write(
"version: 1\ngraphics:\n frame_rate_limit: 60\n"
"vr:\n enabled: true\n frame_rate_limit: 90\n");
const auto configured = LoadRuntimeConfig(MapEnvironment(), file);
ASSERT_TRUE(configured) << configured.error;
EXPECT_EQ(configured.config.frame_rate().fixed_fps, 60);
EXPECT_EQ(configured.config.vr_frame_rate().fixed_fps, 90);
EXPECT_EQ(configured.config.active_frame_rate().fixed_fps, 90);
const auto desktop =
LoadRuntimeConfig(MapEnvironment({{"MOCKTAIL_VR_ENABLED", "0"},
{"MOCKTAIL_VR_FRAME_RATE_LIMIT", "-1"}}),
file);
ASSERT_TRUE(desktop) << desktop.error;
EXPECT_EQ(desktop.config.vr_frame_rate().mode,
FrameRateLimitMode::kUnmanaged);
EXPECT_EQ(desktop.config.active_frame_rate().fixed_fps, 60);
const auto invalid = LoadRuntimeConfig(
MapEnvironment(),
temporary.Write("version: 1\nvr:\n frame_rate_limit: fastest\n"));
EXPECT_FALSE(invalid);
EXPECT_NE(invalid.error.find("vr.frame_rate_limit"), std::string::npos);
}
TEST(RuntimeConfigFileTest, RejectsMalformedVrSectionAndUnsupportedKeys) {
TemporaryDirectory temporary;
for (const auto* yaml :
{"vr: true\n", "vr:\n enabled: perhaps\n", "vr:\n enabled: [true]\n",
"vr:\n enable: true\n", "vr:\n enabled: true\n enabled: false\n"}) {
const auto result =
LoadRuntimeConfig(MapEnvironment(), temporary.Write(yaml));
EXPECT_FALSE(result) << yaml;
}
}
TEST(RuntimeConfigFileTest, ExportsResolvedVrStateAndRejectsInvalidState) {
const char* previous = std::getenv("MOCKTAIL_VR_ENABLED");
const std::optional<std::string> saved =
previous ? std::optional<std::string>(previous) : std::nullopt;
std::string error;
const auto enabled = RuntimeConfig::FromEnvironment(
MapEnvironment({{"MOCKTAIL_VR_ENABLED", "on"}}));
EXPECT_TRUE(ExportRuntimeConfigEnvironment(enabled, &error)) << error;
EXPECT_STREQ(std::getenv("MOCKTAIL_VR_ENABLED"), "1");
EXPECT_TRUE(ExportRuntimeConfigEnvironment(
RuntimeConfig::FromEnvironment(MapEnvironment()), &error))
<< error;
EXPECT_STREQ(std::getenv("MOCKTAIL_VR_ENABLED"), kDefaultVrEnabled ? "1" : "0");
EXPECT_FALSE(ExportRuntimeConfigEnvironment(
RuntimeConfig::FromEnvironment(
MapEnvironment({{"MOCKTAIL_VR_ENABLED", "invalid"}})),
&error));
EXPECT_STREQ(std::getenv("MOCKTAIL_VR_ENABLED"), kDefaultVrEnabled ? "1" : "0");
if (saved)
setenv("MOCKTAIL_VR_ENABLED", saved->c_str(), 1);
else
unsetenv("MOCKTAIL_VR_ENABLED");
}
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<RuntimeConfigBootstrapResult> results(12);
std::vector<std::thread> 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