mirror of
https://github.com/InstanceBrick/nightcap.git
synced 2026-10-19 11:00:52 +02:00
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.
1365 lines
53 KiB
C++
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
|