diff --git a/Scripts/json_config_parse.py b/Scripts/json_config_parse.py index 7ded1e80e..5c523e8ff 100644 --- a/Scripts/json_config_parse.py +++ b/Scripts/json_config_parse.py @@ -148,7 +148,7 @@ def parse_json(json_text, output_file): OptionRegData = {} OptionMemoryRegions = {} OptionMemoryData = {} - + OptionEnvironmentVariables = {} json_object = json.loads(json_text) json_object = {k.upper(): v for k, v in json_object.items()} @@ -242,6 +242,14 @@ def parse_json(json_text, output_file): length, byte_data = parse_hexstring(data_val) OptionMemoryData[int(data_key, 0)] = (length, byte_data) + if ("ENV" in json_object): + data = json_object["ENV"] + if not (type(data) is dict): + sys.exit("Environment variables value must be list of key:value pairs") + + for data_key, data_val in data.items(): + OptionEnvironmentVariables[data_key] = data_val + # If Match option wasn't touched then set it to the default if (OptionMatch == Regs.REG_INVALID): OptionMatch = Regs.REG_NONE @@ -250,6 +258,7 @@ def parse_json(json_text, output_file): memRegions = bytes() regData = bytes() memData = bytes() + envData = bytes() # Write memory regions for key, val in OptionMemoryRegions.items(): @@ -271,6 +280,13 @@ def parse_json(json_text, output_file): for byte in data: memData += struct.pack('B', byte) + # Write environment variables + for key, val in OptionEnvironmentVariables.items(): + envData += key.encode() + envData += struct.pack('B', 0) + envData += val.encode() + envData += struct.pack('B', 0) + config_file = open(output_file, "wb") config_file.write(struct.pack('Q', OptionMatch.value)) config_file.write(struct.pack('Q', OptionIgnore.value)) @@ -280,7 +296,7 @@ def parse_json(json_text, output_file): config_file.write(struct.pack('I', OptionMode.value)) # Total length of header, including offsets/counts below - headerLength = (8 * 4) + (4 * 2) + (4 * 6) + headerLength = (8 * 4) + (4 * 2) + (4 * 8) offset = headerLength # memory regions offset/count @@ -298,10 +314,16 @@ def parse_json(json_text, output_file): config_file.write(struct.pack('I', len(OptionMemoryData))) offset += len(memData) + # environment data offset/count + config_file.write(struct.pack('I', offset)) + config_file.write(struct.pack('I', len(OptionEnvironmentVariables))) + offset += len(envData) + # write out the actual data for memory regions, reg data and memory data config_file.write(memRegions) config_file.write(regData) config_file.write(memData) + config_file.write(envData) config_file.close() diff --git a/Source/Tests/HarnessHelpers.h b/Source/Tests/HarnessHelpers.h index bf5dee5f3..1f7294f4e 100644 --- a/Source/Tests/HarnessHelpers.h +++ b/Source/Tests/HarnessHelpers.h @@ -161,6 +161,21 @@ namespace FEX::HarnessHelper { void Init(std::string const &ConfigFilename) { ReadFile(ConfigFilename, &RawConfigFile); memcpy(&BaseConfig, RawConfigFile.data(), sizeof(ConfigStructBase)); + GetEnvironmentOptions(); + } + + std::vector> GetEnvironmentOptions() { + std::vector> Env{}; + + uintptr_t DataOffset = BaseConfig.OptionEnvOptionOffset; + for (unsigned i = 0; i < BaseConfig.OptionEnvOptionCount; ++i) { + // Environment variables are null terminated strings + std::string_view Key = RawConfigFile.data() + DataOffset; + std::string_view Value = RawConfigFile.data() + DataOffset + Key.size() + 1; + DataOffset += Key.size() + Value.size() + 2; + Env.emplace_back(Key, Value); + } + return Env; } bool CompareStates(FEXCore::Core::CPUState const* State1, FEXCore::Core::CPUState const* State2) { @@ -316,6 +331,8 @@ namespace FEX::HarnessHelper { uint32_t OptionRegDataCount; uint32_t OptionMemDataOffset; uint32_t OptionMemDataCount; + uint32_t OptionEnvOptionOffset; + uint32_t OptionEnvOptionCount; uint8_t AdditionalData[]; } FEX_PACKED; @@ -424,6 +441,9 @@ namespace FEX::HarnessHelper { Config.LoadMemory(); } + std::vector> GetEnvironmentOptions() { + return Config.GetEnvironmentOptions(); + } bool CompareStates(FEXCore::Core::CPUState const* State1, FEXCore::Core::CPUState const* State2) { return Config.CompareStates(State1, State2); diff --git a/Source/Tests/TestHarnessRunner.cpp b/Source/Tests/TestHarnessRunner.cpp index 318c8d262..7da915560 100644 --- a/Source/Tests/TestHarnessRunner.cpp +++ b/Source/Tests/TestHarnessRunner.cpp @@ -64,6 +64,53 @@ void AssertHandler(char const *Message) { fmt::print("[ASSERT] {}\n", Message); } +namespace { +static const std::vector> EnvConfigLookup = {{ +#define OPT_BASE(type, group, enum, json, default) {"FEX_" #enum, FEXCore::Config::ConfigOption::CONFIG_##enum}, +#include +}}; + +// Claims to be a local application config layer +class TestEnvLoader final : public FEXCore::Config::Layer { +public: + explicit TestEnvLoader(std::vector> _Env) + : FEXCore::Config::Layer(FEXCore::Config::LayerType::LAYER_LOCAL_APP) + , Env {std::move(_Env)} { + Load(); + } + + void Load() override { + std::unordered_map EnvMap; + for (auto &Option : Env) { + std::string_view Key = Option.first; + std::string_view Value = Option.second; + +#define ENVLOADER +#include + + EnvMap.insert_or_assign(Key, Value); + } + + auto GetVar = [&](const std::string_view id) -> std::optional { + const auto it = EnvMap.find(id); + if (it == EnvMap.end()) + return std::nullopt; + + return it->second; + }; + + for (auto &it : EnvConfigLookup) { + if (auto Value = GetVar(it.first); Value) { + Set(it.second, *Value); + } + } + } + +private: + std::vector> Env; +}; +} + int main(int argc, char **argv, char **const envp) { LogMan::Throw::InstallHandler(AssertHandler); LogMan::Msg::InstallHandler(MsgHandler); @@ -80,6 +127,11 @@ int main(int argc, char **argv, char **const envp) { } FEX::HarnessHelper::HarnessCodeLoader Loader{Args[0], Args[1].c_str()}; + + // Adds in environment options from the test harness config + FEXCore::Config::AddLayer(std::make_unique(Loader.GetEnvironmentOptions())); + FEXCore::Config::ReloadMetaLayer(); + FEXCore::Config::Set(FEXCore::Config::CONFIG_IS64BIT_MODE, Loader.Is64BitMode() ? "1" : "0"); FEXCore::Context::InitializeStaticTables(Loader.Is64BitMode() ? FEXCore::Context::MODE_64BIT : FEXCore::Context::MODE_32BIT);