diff --git a/src/test/core/Config_test.cpp b/src/test/core/Config_test.cpp index e3327c8c57..293538990f 100644 --- a/src/test/core/Config_test.cpp +++ b/src/test/core/Config_test.cpp @@ -122,6 +122,83 @@ backend=sqlite return std::format(kConfigContentsTemplate, dbPathSection, valFileSection); } +// setenv and unsetenv are POSIX only, and MSVC does not have them. +#ifndef _MSC_VER + +/** + * Read an environment variable into a value the caller owns. + * + * The value is copied, because POSIX allows a later `setenv` to release the + * string `getenv` returned. + * + * @param name Name of the environment variable to read. + * @return The value, or std::nullopt when the variable is not set. + */ +[[nodiscard]] std::optional +envVar(char const* name) +{ + if (char const* const value = std::getenv(name); value != nullptr) + return std::string{value}; + + return std::nullopt; +} + +/** + * Set an environment variable and restore its previous value when done. + */ +class EnvVarGuard +{ +private: + std::string const name_; + std::optional const saved_; + + /** + * Give an environment variable a value, or remove it. + * + * @param name Name of the environment variable to write. + * @param value Value to set, or std::nullopt to remove the variable. + */ + static void + apply(char const* name, std::optional const& value) noexcept + { + if (value) + { + setenv(name, value->c_str(), 1); + } + else + { + unsetenv(name); + } + } + +public: + /** + * Save the variable's current value, then write the given one. + * + * @param name Name of the environment variable to set. + * @param value Value to set, or std::nullopt to remove the variable. + */ + [[nodiscard]] EnvVarGuard(char const* name, std::optional const& value) + : name_(name), saved_(envVar(name)) + { + apply(name_.c_str(), value); + } + + EnvVarGuard(EnvVarGuard const&) = delete; + EnvVarGuard& + operator=(EnvVarGuard const&) = delete; + + /** + * Restore the value the variable held before construction. + */ + ~EnvVarGuard() + { + apply(name_.c_str(), saved_); + } +}; + +#endif // _MSC_VER + /** * Write an xrpld config file and remove when done. */ @@ -335,7 +412,11 @@ port_wss_admin } // Config file in HOME or XDG_CONFIG_HOME directory. -#if BOOST_OS_LINUX || BOOST_OS_MACOS +#ifndef _MSC_VER + // Save the values the guards below must put back. + auto const home = detail::envVar("HOME"); + auto const xdgConfigHome = detail::envVar("XDG_CONFIG_HOME"); + for (auto const& configFile : configFiles) { // Point the current working directory to a temporary directory, so @@ -348,13 +429,11 @@ port_wss_admin { TempDir const tc; - // Set the HOME and XDG_CONFIG_HOME environment variables. The - // HOME variable is not used when XDG_CONFIG_HOME is set, but - // must be set. - char const* h = getenv("HOME"); - setenv("HOME", tc.path().c_str(), 1); - char const* x = getenv("XDG_CONFIG_HOME"); - setenv("XDG_CONFIG_HOME", tc.path().c_str(), 1); + // The HOME variable is not used when XDG_CONFIG_HOME is set, + // but must be set. Both guards are declared after tc, so they + // restore before it is removed. + detail::EnvVarGuard const homeGuard("HOME", tc.path()); + detail::EnvVarGuard const xdgGuard("XDG_CONFIG_HOME", tc.path()); // Create the config file in '${XDG_CONFIG_HOME}/[systemName]'. Path p = tc.file(systemName()); @@ -371,22 +450,21 @@ port_wss_admin BEAST_EXPECT( c.section(Sections::kDebugLogfile).values()[0] == "/Users/dummy/xrpld/config/log/debug.log"); - - // Restore the environment variables. - (h != nullptr) ? setenv("HOME", h, 1) : unsetenv("HOME"); - (x != nullptr) ? setenv("XDG_CONFIG_HOME", x, 1) : unsetenv("XDG_CONFIG_HOME"); } + // Both guards are gone: both variables must hold their old values. + BEAST_EXPECT(detail::envVar("HOME") == home); + BEAST_EXPECT(detail::envVar("XDG_CONFIG_HOME") == xdgConfigHome); + // The XDG config directory is not set: the config file must be in a // subdirectory named .config followed by the system name. { TempDir const tc; - // Set only the HOME environment variable. - char const* h = getenv("HOME"); - setenv("HOME", tc.path().c_str(), 1); - char const* x = getenv("XDG_CONFIG_HOME"); - unsetenv("XDG_CONFIG_HOME"); + // Both guards are declared after tc, so they restore before it + // is removed. + detail::EnvVarGuard const homeGuard("HOME", tc.path()); + detail::EnvVarGuard const xdgGuard("XDG_CONFIG_HOME", std::nullopt); // Create the config file in '${HOME}/.config/[systemName]'. std::string s = ".config"; @@ -407,14 +485,13 @@ port_wss_admin BEAST_EXPECT( c.section(Sections::kDebugLogfile).values()[0] == "/Users/dummy/xrpld/config/log/debug.log"); - - // Restore the environment variables. - (h != nullptr) ? setenv("HOME", h, 1) : unsetenv("HOME"); - if (x != nullptr) - setenv("XDG_CONFIG_HOME", x, 1); } + + // Both guards are gone: both variables must hold their old values. + BEAST_EXPECT(detail::envVar("HOME") == home); + BEAST_EXPECT(detail::envVar("XDG_CONFIG_HOME") == xdgConfigHome); } -#endif +#endif // _MSC_VER // Restore the current working directory. current_path(cwd);