Files
TheRock/rocm-systems/projects/rocprofiler-systems/source/lib/common/tests/test_environment.cpp
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

739 lines
25 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright (c) Advanced Micro Devices, Inc.
// SPDX-License-Identifier: MIT
#include "common/environment.hpp"
#include <cstdint>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <unordered_map>
using namespace rocprofsys::common;
namespace env_vars = rocprofsys::env_vars;
// ── fake_env ─────────────────────────────────────────────────────────────────
// In-memory environment backend for unit tests.
// Call fake_env::reset() in SetUp()/TearDown() to isolate tests.
struct fake_env
{
inline static std::unordered_map<std::string, std::string> store;
static int setenv(const char* name, const char* value, int overwrite)
{
if(!overwrite && store.count(name)) return 0;
store[name] = value;
return 0;
}
static char* getenv(const char* name)
{
auto it = store.find(name);
return it != store.end() ? it->second.data() : nullptr;
}
static void reset() { store.clear(); }
};
// Convenience alias used throughout the injection tests.
using fake_environment = environment<fake_env>;
class IsPythonInterpreterTest : public ::testing::Test
{};
TEST_F(IsPythonInterpreterTest, RecognizesPython)
{
EXPECT_TRUE(is_python_interpreter("python"));
EXPECT_TRUE(is_python_interpreter("python3"));
EXPECT_TRUE(is_python_interpreter("python3.8"));
EXPECT_TRUE(is_python_interpreter("python3.9"));
EXPECT_TRUE(is_python_interpreter("python3.10"));
EXPECT_TRUE(is_python_interpreter("python3.11"));
EXPECT_TRUE(is_python_interpreter("python3.12"));
EXPECT_TRUE(is_python_interpreter("/usr/bin/python"));
EXPECT_TRUE(is_python_interpreter("/usr/bin/python3"));
EXPECT_TRUE(is_python_interpreter("/usr/bin/python3.10"));
EXPECT_TRUE(is_python_interpreter("/home/user/venv/bin/python"));
EXPECT_TRUE(is_python_interpreter("/opt/conda/bin/python3.11"));
EXPECT_FALSE(is_python_interpreter("bash"));
EXPECT_FALSE(is_python_interpreter("sh"));
EXPECT_FALSE(is_python_interpreter("ruby"));
EXPECT_FALSE(is_python_interpreter("node"));
EXPECT_FALSE(is_python_interpreter("java"));
EXPECT_FALSE(is_python_interpreter("/usr/bin/bash"));
EXPECT_FALSE(is_python_interpreter("./my_app"));
EXPECT_FALSE(is_python_interpreter("pythonista"));
EXPECT_FALSE(is_python_interpreter("python_script.py"));
EXPECT_FALSE(is_python_interpreter("mypython"));
EXPECT_FALSE(is_python_interpreter("python2"));
EXPECT_FALSE(is_python_interpreter("python3."));
EXPECT_FALSE(is_python_interpreter("python3.a"));
EXPECT_FALSE(is_python_interpreter("python3.10a"));
EXPECT_FALSE(is_python_interpreter("python3x10"));
EXPECT_FALSE(is_python_interpreter(""));
EXPECT_FALSE(is_python_interpreter("/usr/bin/"));
}
class DuplicatedEnvironmentEntriesTest : public ::testing::Test
{};
TEST_F(DuplicatedEnvironmentEntriesTest, DuplicateEnvironmentEntries)
{
std::vector<std::string> env_vars = {
"PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/bin2",
"PATH=/usr/local/bin:/usr/bin:/bin",
};
consolidate_env_entries(env_vars);
ASSERT_EQ(env_vars.size(), 1);
EXPECT_EQ(env_vars[0], "PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/bin2");
}
TEST_F(DuplicatedEnvironmentEntriesTest, HandlesEmptyVector)
{
std::vector<std::string> env_vars;
consolidate_env_entries(env_vars);
EXPECT_TRUE(env_vars.empty());
}
TEST_F(DuplicatedEnvironmentEntriesTest, HandlesEmptyValues)
{
std::vector<std::string> env_vars = {
"EMPTY_VAR=",
"PATH=/usr/bin",
};
consolidate_env_entries(env_vars);
ASSERT_EQ(env_vars.size(), 2);
}
TEST_F(DuplicatedEnvironmentEntriesTest, PapiEventsUsesCommaDelimiter)
{
std::vector<std::string> env_vars = {
std::string{ env_vars::PAPI_EVENTS } + "=perf::INSTRUCTIONS",
std::string{ env_vars::PAPI_EVENTS } + "=perf::CACHE_MISSES",
};
consolidate_env_entries(env_vars);
ASSERT_EQ(env_vars.size(), 1);
EXPECT_EQ(env_vars[0], std::string{ env_vars::PAPI_EVENTS } +
"=perf::INSTRUCTIONS,perf::CACHE_MISSES");
}
TEST_F(DuplicatedEnvironmentEntriesTest, PapiEventsPreservesColonInValue)
{
std::vector<std::string> env_vars = {
std::string{ env_vars::PAPI_EVENTS } + "=perf::PERF_COUNT_SW_CPU_CLOCK",
};
consolidate_env_entries(env_vars);
ASSERT_EQ(env_vars.size(), 1);
EXPECT_EQ(env_vars[0],
std::string{ env_vars::PAPI_EVENTS } + "=perf::PERF_COUNT_SW_CPU_CLOCK");
}
TEST_F(DuplicatedEnvironmentEntriesTest, PapiEventsDeduplicates)
{
std::vector<std::string> env_vars = {
std::string{ env_vars::PAPI_EVENTS } + "=perf::INSTRUCTIONS",
std::string{ env_vars::PAPI_EVENTS } + "=perf::CACHE_MISSES",
std::string{ env_vars::PAPI_EVENTS } + "=perf::INSTRUCTIONS",
};
consolidate_env_entries(env_vars);
ASSERT_EQ(env_vars.size(), 1);
EXPECT_EQ(env_vars[0], std::string{ env_vars::PAPI_EVENTS } +
"=perf::INSTRUCTIONS,perf::CACHE_MISSES");
}
TEST_F(DuplicatedEnvironmentEntriesTest, SamplingOverflowEventUsesCommaDelimiter)
{
std::vector<std::string> env_vars = {
std::string{ env_vars::SAMPLING_OVERFLOW_EVENT } + "=perf::INSTRUCTIONS",
std::string{ env_vars::SAMPLING_OVERFLOW_EVENT } + "=perf::CYCLES",
};
consolidate_env_entries(env_vars);
ASSERT_EQ(env_vars.size(), 1);
EXPECT_EQ(env_vars[0], std::string{ env_vars::SAMPLING_OVERFLOW_EVENT } +
"=perf::INSTRUCTIONS,perf::CYCLES");
}
TEST_F(DuplicatedEnvironmentEntriesTest, MixedDelimiterVariables)
{
std::vector<std::string> env_vars = {
"PATH=/usr/bin",
std::string{ env_vars::PAPI_EVENTS } + "=perf::INSTRUCTIONS",
"PATH=/usr/local/bin",
std::string{ env_vars::PAPI_EVENTS } + "=perf::CACHE_MISSES",
"LD_LIBRARY_PATH=/lib",
};
consolidate_env_entries(env_vars);
ASSERT_EQ(env_vars.size(), 3);
EXPECT_EQ(env_vars[0], "PATH=/usr/bin:/usr/local/bin");
EXPECT_EQ(env_vars[1], std::string{ env_vars::PAPI_EVENTS } +
"=perf::INSTRUCTIONS,perf::CACHE_MISSES");
EXPECT_EQ(env_vars[2], "LD_LIBRARY_PATH=/lib");
}
TEST_F(DuplicatedEnvironmentEntriesTest, PreservesKeyOrder)
{
std::vector<std::string> env_vars = {
"ZEBRA=1",
"ALPHA=2",
"MIDDLE=3",
};
consolidate_env_entries(env_vars);
ASSERT_EQ(env_vars.size(), 3);
EXPECT_EQ(env_vars[0], "ZEBRA=1");
EXPECT_EQ(env_vars[1], "ALPHA=2");
EXPECT_EQ(env_vars[2], "MIDDLE=3");
}
TEST_F(DuplicatedEnvironmentEntriesTest, PapiEventsWithCommaInValue)
{
std::vector<std::string> env_vars = {
std::string{ env_vars::PAPI_EVENTS } + "=perf::INSTRUCTIONS,perf::CYCLES",
std::string{ env_vars::PAPI_EVENTS } + "=perf::CACHE_MISSES",
};
consolidate_env_entries(env_vars);
ASSERT_EQ(env_vars.size(), 1);
EXPECT_EQ(env_vars[0], std::string{ env_vars::PAPI_EVENTS } +
"=perf::INSTRUCTIONS,perf::CYCLES,perf::CACHE_MISSES");
}
TEST_F(DuplicatedEnvironmentEntriesTest, RocmEventsUsesCommaDelimiter)
{
std::vector<std::string> env_vars = {
std::string{ env_vars::ROCM_EVENTS } + "=SQ_WAVES:device=0",
std::string{ env_vars::ROCM_EVENTS } + "=TA_TA_BUSY:device=1",
};
consolidate_env_entries(env_vars);
ASSERT_EQ(env_vars.size(), 1);
EXPECT_EQ(env_vars[0], std::string{ env_vars::ROCM_EVENTS } +
"=SQ_WAVES:device=0,TA_TA_BUSY:device=1");
}
TEST_F(DuplicatedEnvironmentEntriesTest, RocmEventsPreservesDeviceSyntax)
{
std::vector<std::string> env_vars = {
std::string{ env_vars::ROCM_EVENTS } +
"=GRBM_COUNT,SQ_WAVES,SQ_INSTS_VALU,TA_TA_BUSY:device=0",
};
consolidate_env_entries(env_vars);
ASSERT_EQ(env_vars.size(), 1);
EXPECT_EQ(env_vars[0], std::string{ env_vars::ROCM_EVENTS } +
"=GRBM_COUNT,SQ_WAVES,SQ_INSTS_VALU,TA_TA_BUSY:device=0");
}
class AddTorchLibraryPathTest : public ::testing::Test
{
protected:
std::unordered_set<std::string> updated_envs;
};
TEST_F(AddTorchLibraryPathTest, SkipsNonPythonExecutables)
{
std::vector<std::string> envp = { "LD_LIBRARY_PATH=/usr/lib" };
add_torch_library_path(envp, "/usr/bin/bash", updated_envs);
ASSERT_EQ(envp.size(), 1);
EXPECT_EQ(envp[0], "LD_LIBRARY_PATH=/usr/lib");
}
TEST_F(AddTorchLibraryPathTest, HandlesEmptyExecutable)
{
std::vector<std::string> envp = { "LD_LIBRARY_PATH=/usr/lib" };
add_torch_library_path(envp, std::string_view{}, updated_envs);
ASSERT_EQ(envp.size(), 1);
EXPECT_EQ(envp[0], "LD_LIBRARY_PATH=/usr/lib");
}
TEST_F(AddTorchLibraryPathTest, PythonExecutableWithoutTorchLeavesEnvUnchanged)
{
// "/nonexistent/python3" is recognised as a Python interpreter but
// discover_torch_libpath returns "" → early return, env unchanged.
std::vector<std::string> envp = { "LD_LIBRARY_PATH=/usr/lib" };
add_torch_library_path(envp, "/nonexistent/python3", updated_envs);
ASSERT_EQ(envp.size(), 1);
EXPECT_EQ(envp[0], "LD_LIBRARY_PATH=/usr/lib");
}
// ── discover_torch_libpath direct tests (L384–L442) ──────────────────────────
TEST(DiscoverTorchLibpathTest, EmptyBinaryReturnsEmpty)
{
EXPECT_EQ(discover_torch_libpath(""), "");
}
TEST(DiscoverTorchLibpathTest, UnsafeCharInPathReturnsEmpty)
{
// ';' hits default: return false in is_safe_executable_path
EXPECT_EQ(discover_torch_libpath("/usr/bin/python;injected"), "");
}
TEST(DiscoverTorchLibpathTest, SpaceInPathReturnsEmpty)
{
EXPECT_EQ(discover_torch_libpath("/path with space/python3"), "");
}
TEST(DiscoverTorchLibpathTest, DollarSignInPathReturnsEmpty)
{
EXPECT_EQ(discover_torch_libpath("$HOME/python3"), "");
}
TEST(DiscoverTorchLibpathTest, SafePathWithAllowedCharsAndNoTorchReturnsEmpty)
{
// Path uses all allowed non-alnum chars (/ _ - + .) to exercise every
// switch case in is_safe_executable_path. Shell exits non-zero (not found)
// → popen succeeds, status != 0 → returns empty string.
EXPECT_EQ(discover_torch_libpath("/nonexistent_dir/python-3.11+safe.bin"), "");
}
// ── posix_env + forwarding free functions (L77, L79, L269–L279) ──────────────
// Calling get_env / set_env without the fake_environment:: prefix exercises
// posix_env::getenv / setenv through the real environment<posix_env>.
TEST(FreeFunctionEnvTest, GetEnvReturnsDefaultForUnsetVar)
{
// Unset variable → default returned; covers posix_env::getenv + free-func body
EXPECT_EQ(get_env("ROCPROFSYS_FREE_FUNC_TEST_UNSET_99887766", std::string{ "dflt" }),
"dflt");
}
TEST(FreeFunctionEnvTest, GetEnvOneArgReturnsEmptyForUnsetVar)
{
EXPECT_EQ(get_env<std::string>("ROCPROFSYS_FREE_FUNC_TEST_UNSET_99887766"), "");
}
TEST(FreeFunctionEnvTest, SetEnvAndGetViaFreeFunction)
{
const char* name = "ROCPROFSYS_FREE_FUNC_SETENV_TEST_99887766";
set_env(name, std::string{ "free_val" }, 1);
EXPECT_EQ(get_env(name, std::string{}), "free_val");
::unsetenv(name);
}
// ── Enum used by the dispatch tests below ────────────────────────────────────
enum class test_color : int
{
red = 0,
green = 1,
blue = 2,
};
// ── Dependency-injection tests via fake_env ───────────────────────────────────
// These tests never touch the real process environment.
class FakeEnvGetEnvTest : public ::testing::Test
{
protected:
void SetUp() override { fake_env::reset(); }
void TearDown() override { fake_env::reset(); }
};
TEST_F(FakeEnvGetEnvTest, StringReturnsDefaultWhenUnset)
{
EXPECT_EQ(fake_environment::get_env("FOO", std::string{ "default" }), "default");
}
TEST_F(FakeEnvGetEnvTest, StringReturnsValueWhenSet)
{
fake_env::setenv("FOO", "bar", 1);
EXPECT_EQ(fake_environment::get_env("FOO", std::string{ "default" }), "bar");
}
TEST_F(FakeEnvGetEnvTest, IntReturnsDefaultWhenUnset)
{
EXPECT_EQ(fake_environment::get_env("FOO", 42), 42);
}
TEST_F(FakeEnvGetEnvTest, IntReturnsValueWhenSet)
{
fake_env::setenv("FOO", "7", 1);
EXPECT_EQ(fake_environment::get_env("FOO", 42), 7);
}
TEST_F(FakeEnvGetEnvTest, BoolTrueVariants)
{
for(const char* v : { "1", "true", "yes", "on" })
{
fake_env::reset();
fake_env::setenv("FOO", v, 1);
EXPECT_TRUE(fake_environment::get_env("FOO", false)) << "value: " << v;
}
}
TEST_F(FakeEnvGetEnvTest, BoolFalseVariants)
{
for(const char* v : { "0", "false", "no", "off" })
{
fake_env::reset();
fake_env::setenv("FOO", v, 1);
EXPECT_FALSE(fake_environment::get_env("FOO", true)) << "value: " << v;
}
}
TEST_F(FakeEnvGetEnvTest, DoubleReturnsValueWhenSet)
{
fake_env::setenv("FOO", "3.14", 1);
EXPECT_NEAR(fake_environment::get_env("FOO", 0.0), 3.14, 1e-9);
}
TEST_F(FakeEnvGetEnvTest, OneArgFormReturnsEmptyWhenUnset)
{
EXPECT_EQ(fake_environment::get_env<std::string>("FOO"), "");
}
TEST_F(FakeEnvGetEnvTest, EmptyVarNameReturnsDefault)
{
EXPECT_EQ(fake_environment::get_env("", std::string{ "fallback" }), "fallback");
}
TEST_F(FakeEnvGetEnvTest, DoesNotLeakToRealEnvironment)
{
fake_env::setenv("ROCPROFSYS_FAKE_TEST_ISOLATION", "injected", 1);
// The real process environment must not have been modified.
EXPECT_EQ(::getenv("ROCPROFSYS_FAKE_TEST_ISOLATION"), nullptr);
}
class FakeEnvSetEnvTest : public ::testing::Test
{
protected:
void SetUp() override { fake_env::reset(); }
void TearDown() override { fake_env::reset(); }
};
TEST_F(FakeEnvSetEnvTest, SetsStringValue)
{
fake_environment::set_env("FOO", std::string{ "hello" }, 1);
EXPECT_EQ(fake_environment::get_env("FOO", std::string{}), "hello");
}
TEST_F(FakeEnvSetEnvTest, SetsIntValue)
{
fake_environment::set_env("FOO", 99, 1);
EXPECT_EQ(fake_environment::get_env("FOO", 0), 99);
}
TEST_F(FakeEnvSetEnvTest, OverrideZeroDoesNotOverwrite)
{
fake_env::setenv("FOO", "original", 1);
fake_environment::set_env("FOO", std::string{ "new" }, 0);
EXPECT_EQ(fake_environment::get_env("FOO", std::string{}), "original");
}
TEST_F(FakeEnvSetEnvTest, OverrideOneOverwrites)
{
fake_env::setenv("FOO", "original", 1);
fake_environment::set_env("FOO", std::string{ "new" }, 1);
EXPECT_EQ(fake_environment::get_env("FOO", std::string{}), "new");
}
class FakeEnvGetEnvChoiceTest : public ::testing::Test
{
protected:
void SetUp() override { fake_env::reset(); }
void TearDown() override { fake_env::reset(); }
};
TEST_F(FakeEnvGetEnvChoiceTest, ReturnsDefaultWhenUnset)
{
auto result = fake_environment::get_env_choice<std::string>(
"FOO", "trace", { "trace", "sampling", "causal" });
EXPECT_EQ(result, "trace");
}
TEST_F(FakeEnvGetEnvChoiceTest, ReturnsValueWhenValidChoiceSet)
{
fake_env::setenv("FOO", "sampling", 1);
auto result = fake_environment::get_env_choice<std::string>(
"FOO", "trace", { "trace", "sampling", "causal" });
EXPECT_EQ(result, "sampling");
}
TEST_F(FakeEnvGetEnvChoiceTest, ReturnsDefaultWhenInvalidChoiceSet)
{
fake_env::setenv("FOO", "bad_value", 1);
auto result = fake_environment::get_env_choice<std::string>(
"FOO", "trace", { "trace", "sampling", "causal" });
EXPECT_EQ(result, "trace");
}
// ── Bool branch gaps ──────────────────────────────────────────────────────────
TEST_F(FakeEnvGetEnvTest, BoolEmptyEnvIdReturnsDefault)
{
EXPECT_FALSE(fake_environment::get_env("", false));
EXPECT_TRUE(fake_environment::get_env("", true));
}
TEST_F(FakeEnvGetEnvTest, BoolEmptyValueThrows)
{
fake_env::setenv("FOO", "", 1);
EXPECT_THROW(fake_environment::get_env("FOO", false), std::runtime_error);
}
TEST_F(FakeEnvGetEnvTest, BoolNumericGreaterThanOneIsTrue)
{
fake_env::setenv("FOO", "2", 1);
EXPECT_TRUE(fake_environment::get_env("FOO", false));
}
TEST_F(FakeEnvGetEnvTest, BoolNumericOverflowIsTrueAndDoesNotThrow)
{
// An all-digit value past UINT64_MAX must not throw (std::stoi would) and
// is still truthy.
fake_env::setenv("FOO", "99999999999999999999999", 1);
EXPECT_NO_THROW({ EXPECT_TRUE(fake_environment::get_env("FOO", false)); });
}
TEST_F(FakeEnvGetEnvTest, BoolUpperCaseFalseVariants)
{
for(const char* v : { "FALSE", "NO", "OFF", "F", "N" })
{
fake_env::reset();
fake_env::setenv("FOO", v, 1);
EXPECT_FALSE(fake_environment::get_env("FOO", true)) << "value: " << v;
}
}
// ── Float branch gaps ─────────────────────────────────────────────────────────
TEST_F(FakeEnvGetEnvTest, FloatEmptyEnvIdReturnsDefault)
{
EXPECT_NEAR(fake_environment::get_env("", 1.5), 1.5, 1e-9);
}
TEST_F(FakeEnvGetEnvTest, FloatInvalidStringReturnsDefault)
{
fake_env::setenv("FOO", "not_a_float", 1);
EXPECT_NEAR(fake_environment::get_env("FOO", 9.9), 9.9, 1e-9);
}
TEST_F(FakeEnvGetEnvTest, FloatTypeReturnsValue)
{
fake_env::setenv("FOO", "1.5", 1);
EXPECT_NEAR(fake_environment::get_env("FOO", 0.0f), 1.5f, 1e-6f);
}
TEST_F(FakeEnvGetEnvTest, FloatSurroundingWhitespaceParses)
{
fake_env::setenv("FOO", " 2.5 ", 1);
EXPECT_NEAR(fake_environment::get_env("FOO", 0.0), 2.5, 1e-9);
}
TEST_F(FakeEnvGetEnvTest, FloatTrailingGarbageReturnsDefault)
{
fake_env::setenv("FOO", "1.5abc", 1);
EXPECT_NEAR(fake_environment::get_env("FOO", 9.9), 9.9, 1e-9);
}
// ── Integral branch gaps ──────────────────────────────────────────────────────
TEST_F(FakeEnvGetEnvTest, IntEmptyEnvIdReturnsDefault)
{
EXPECT_EQ(fake_environment::get_env("", 99), 99);
}
TEST_F(FakeEnvGetEnvTest, IntInvalidStringReturnsDefault)
{
fake_env::setenv("FOO", "not_an_int", 1);
EXPECT_EQ(fake_environment::get_env("FOO", 42), 42);
}
TEST_F(FakeEnvGetEnvTest, LongReturnsValueWhenSet)
{
fake_env::setenv("FOO", "123456789012", 1);
EXPECT_EQ(fake_environment::get_env("FOO", 0L), 123456789012L);
}
TEST_F(FakeEnvGetEnvTest, SizeTReturnsValueWhenSet)
{
fake_env::setenv("FOO", "42", 1);
EXPECT_EQ(fake_environment::get_env("FOO", std::size_t{ 0 }), std::size_t{ 42 });
}
TEST_F(FakeEnvGetEnvTest, UnsignedNegativeInputReturnsDefault)
{
// "-1" must not wrap to UINT64_MAX for an unsigned target; fall back instead.
fake_env::setenv("FOO", "-1", 1);
EXPECT_EQ(fake_environment::get_env("FOO", std::uint64_t{ 7 }), std::uint64_t{ 7 });
}
TEST_F(FakeEnvGetEnvTest, UnsignedOutOfRangeInputReturnsDefault)
{
// 70000 does not fit in std::uint16_t -> fall back to the default.
fake_env::setenv("FOO", "70000", 1);
EXPECT_EQ(fake_environment::get_env("FOO", std::uint16_t{ 5 }), std::uint16_t{ 5 });
}
TEST_F(FakeEnvGetEnvTest, UnsignedLargeValuePastInt64MaxParses)
{
// Value > INT64_MAX previously threw out_of_range via stoll; it must now
// parse correctly for an unsigned 64-bit target.
constexpr auto big = std::uint64_t{ 18446744073709551610ULL }; // UINT64_MAX - 5
fake_env::setenv("FOO", "18446744073709551610", 1);
EXPECT_EQ(fake_environment::get_env("FOO", std::uint64_t{ 0 }), big);
}
TEST_F(FakeEnvGetEnvTest, SignedNegativeInputParses)
{
// Signed targets must still accept negative values (e.g. ROCPROFSYS_VERBOSE).
fake_env::setenv("FOO", "-3", 1);
EXPECT_EQ(fake_environment::get_env("FOO", 0), -3);
}
TEST_F(FakeEnvGetEnvTest, SignedOutOfRangeInputReturnsDefault)
{
// 70000 does not fit in std::int16_t -> fall back to the default.
fake_env::setenv("FOO", "70000", 1);
EXPECT_EQ(fake_environment::get_env("FOO", std::int16_t{ 9 }), std::int16_t{ 9 });
}
TEST_F(FakeEnvGetEnvTest, IntegralTrailingGarbageReturnsDefault)
{
fake_env::setenv("FOO", "42abc", 1);
EXPECT_EQ(fake_environment::get_env("FOO", 1), 1);
}
TEST_F(FakeEnvGetEnvTest, IntegralSurroundingWhitespaceParses)
{
fake_env::setenv("FOO", " 42 ", 1);
EXPECT_EQ(fake_environment::get_env("FOO", 0), 42);
}
// ── to_env_string ─────────────────────────────────────────────────────────────
TEST(ToEnvStringTest, BoolTrue) { EXPECT_EQ(to_env_string(true), "true"); }
TEST(ToEnvStringTest, BoolFalse) { EXPECT_EQ(to_env_string(false), "false"); }
TEST(ToEnvStringTest, Int) { EXPECT_EQ(to_env_string(42), "42"); }
TEST(ToEnvStringTest, Double) { EXPECT_EQ(to_env_string(3.14), std::to_string(3.14)); }
TEST(ToEnvStringTest, StringPassthrough)
{
EXPECT_EQ(to_env_string(std::string{ "hello" }), "hello");
}
TEST(ToEnvStringTest, ConstCharPtrPassthrough)
{
EXPECT_EQ(to_env_string("world"), std::string{ "world" });
}
// ── get_default_lib_search_paths ──────────────────────────────────────────────
TEST(GetDefaultLibSearchPathsTest, ReturnsNonEmpty)
{
auto paths = get_default_lib_search_paths();
EXPECT_FALSE(paths.empty());
}
// ── consolidate_env_entries: no-'=' entry ─────────────────────────────────────
TEST_F(DuplicatedEnvironmentEntriesTest, SkipsEntryWithoutEqualsSign)
{
std::vector<std::string> env_vars = { "NO_EQUALS", "PATH=/usr/bin" };
consolidate_env_entries(env_vars);
ASSERT_EQ(env_vars.size(), 1);
EXPECT_EQ(env_vars[0], "PATH=/usr/bin");
}
class FakeEnvConfigTest : public ::testing::Test
{
protected:
void SetUp() override { fake_env::reset(); }
void TearDown() override { fake_env::reset(); }
};
TEST_F(FakeEnvConfigTest, OperatorSetsValue)
{
env_config<fake_env> cfg;
cfg.m_env_name = "FOO";
cfg.m_env_value = "injected";
cfg.m_override = 1;
cfg();
EXPECT_EQ(fake_environment::get_env("FOO", std::string{}), "injected");
}
TEST_F(FakeEnvConfigTest, OperatorRespectsOverrideZero)
{
fake_env::setenv("FOO", "original", 1);
env_config<fake_env> cfg;
cfg.m_env_name = "FOO";
cfg.m_env_value = "new";
cfg.m_override = 0;
cfg();
EXPECT_EQ(fake_environment::get_env("FOO", std::string{}), "original");
}
TEST_F(FakeEnvConfigTest, OperatorReturnsZeroOnSuccess)
{
env_config<fake_env> cfg;
cfg.m_env_name = "FOO";
cfg.m_env_value = "some_value";
cfg.m_override = 1;
EXPECT_EQ(cfg(), 0);
EXPECT_EQ(fake_environment::get_env("FOO", std::string{}), "some_value");
}
TEST_F(FakeEnvConfigTest, EmptyNameIsNoop)
{
env_config<fake_env> cfg;
cfg.m_env_name = "";
cfg.m_env_value = "ignored";
cfg.m_override = 1;
EXPECT_EQ(cfg(), -1);
EXPECT_TRUE(fake_env::store.empty());
}
// ── Enum dispatch (get_env<Tp> where is_enum_v<Tp>) ──────────────────────────
class FakeEnvEnumTest : public ::testing::Test
{
protected:
void SetUp() override { fake_env::reset(); }
void TearDown() override { fake_env::reset(); }
};
TEST_F(FakeEnvEnumTest, EnumReturnsDefaultWhenUnset)
{
EXPECT_EQ(fake_environment::get_env("FOO", test_color::green), test_color::green);
}
TEST_F(FakeEnvEnumTest, EnumReturnsValueWhenSet)
{
fake_env::setenv("FOO", "2", 1);
EXPECT_EQ(fake_environment::get_env("FOO", test_color::red), test_color::blue);
}
TEST_F(FakeEnvEnumTest, EnumEmptyEnvIdReturnsDefault)
{
EXPECT_EQ(fake_environment::get_env("", test_color::green), test_color::green);
}
// ── get_env_choice with non-string type ───────────────────────────────────────
class FakeEnvIntChoiceTest : public ::testing::Test
{
protected:
void SetUp() override { fake_env::reset(); }
void TearDown() override { fake_env::reset(); }
};
TEST_F(FakeEnvIntChoiceTest, ReturnsDefaultWhenUnset)
{
auto result = fake_environment::get_env_choice("FOO", 1, std::set<int>{ 1, 2, 3 });
EXPECT_EQ(result, 1);
}
TEST_F(FakeEnvIntChoiceTest, ReturnsValueWhenValidChoiceSet)
{
fake_env::setenv("FOO", "3", 1);
auto result = fake_environment::get_env_choice("FOO", 1, std::set<int>{ 1, 2, 3 });
EXPECT_EQ(result, 3);
}
TEST_F(FakeEnvIntChoiceTest, ReturnsDefaultWhenInvalidChoiceSet)
{
fake_env::setenv("FOO", "99", 1);
auto result = fake_environment::get_env_choice("FOO", 1, std::set<int>{ 1, 2, 3 });
EXPECT_EQ(result, 1);
}