| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <algorithm> | ||
| 4 | #include <array> | ||
| 5 | #include <atomic> | ||
| 6 | #include <cctype> | ||
| 7 | #include <filesystem> | ||
| 8 | #include <memory> | ||
| 9 | #include <sstream> | ||
| 10 | #include <string> | ||
| 11 | #include <string_view> | ||
| 12 | #include <system_error> | ||
| 13 | |||
| 14 | #include "nlohmann/json_fwd.hpp" | ||
| 15 | |||
| 16 | #ifdef _MSC_VER | ||
| 17 | # pragma warning(push) | ||
| 18 | # pragma warning(disable : 4459) | ||
| 19 | #endif | ||
| 20 | |||
| 21 | #include <gtest/gtest.h> | ||
| 22 | |||
| 23 | #include <libenvpp/detail/environment.hpp> | ||
| 24 | #include <libenvpp/detail/get.hpp> | ||
| 25 | #include <nlohmann/json.hpp> | ||
| 26 | |||
| 27 | /// @brief JSON namespace used for settings and config parsing. | ||
| 28 | using NlohmannJsonParseError = nlohmann::json::parse_error; | ||
| 29 | /// @brief JSON namespace used for settings and config typing. | ||
| 30 | using NlohmannJsonTypeError = nlohmann::json::type_error; | ||
| 31 | #ifdef _MSC_VER | ||
| 32 | # pragma warning(pop) | ||
| 33 | #endif | ||
| 34 | |||
| 35 | namespace ppc::util { | ||
| 36 | |||
| 37 | /// @brief Utility class for tracking destructor failure across tests. | ||
| 38 | /// @details Provides thread-safe methods to set, unset, and check the failure flag. | ||
| 39 | class DestructorFailureFlag { | ||
| 40 | public: | ||
| 41 | /// @brief Marks that a destructor failure has occurred. | ||
| 42 | static void Set() { | ||
| 43 | failure_flag.store(true); | ||
| 44 | 20 | } | |
| 45 | |||
| 46 | /// @brief Clears the destructor failure flag. | ||
| 47 | static void Unset() { | ||
| 48 | failure_flag.store(false); | ||
| 49 | } | ||
| 50 | |||
| 51 | /// @brief Checks if a destructor failure was recorded. | ||
| 52 | /// @return True if failure occurred, false otherwise. | ||
| 53 | static bool Get() { | ||
| 54 | return failure_flag.load(); | ||
| 55 | } | ||
| 56 | |||
| 57 | private: | ||
| 58 | inline static std::atomic<bool> failure_flag{false}; | ||
| 59 | }; | ||
| 60 | |||
| 61 | class PerformanceFailureFlag { | ||
| 62 | public: | ||
| 63 | static void Set() { | ||
| 64 | failure_flag.store(true); | ||
| 65 | } | ||
| 66 | |||
| 67 | static void Unset() { | ||
| 68 | failure_flag.store(false); | ||
| 69 | } | ||
| 70 | |||
| 71 | static bool Get() { | ||
| 72 | return failure_flag.load(); | ||
| 73 | } | ||
| 74 | |||
| 75 | private: | ||
| 76 | inline static std::atomic<bool> failure_flag{false}; | ||
| 77 | }; | ||
| 78 | |||
| 79 | std::string GetAbsoluteTaskPath(const std::string &id_path, const std::string &relative_path); | ||
| 80 | int GetNumThreads(); | ||
| 81 | int GetNumProc(); | ||
| 82 | double GetTaskMaxTime(); | ||
| 83 | double GetPerfMaxTime(); | ||
| 84 | double GetTimeMPI(); | ||
| 85 | int GetMPIRank(); | ||
| 86 | void ConfigureMpiEnvironment(); | ||
| 87 | void SynchronizeMpiRanks(); | ||
| 88 | |||
| 89 | inline std::shared_ptr<nlohmann::json> InitJSONPtr() { | ||
| 90 | return std::make_shared<nlohmann::json>(); | ||
| 91 | } | ||
| 92 | |||
| 93 | bool IsUnderMpirun(); | ||
| 94 | |||
| 95 | namespace test { | ||
| 96 | |||
| 97 | 168 | [[nodiscard]] inline std::string SanitizeToken(std::string_view token_sv) { | |
| 98 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 168 times.
|
168 | std::string token{token_sv}; |
| 99 | 8976 | auto is_allowed = [](char c) -> bool { | |
| 100 |
4/6✓ Branch 0 taken 168 times.
✓ Branch 1 taken 8808 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 168 times.
✓ Branch 4 taken 168 times.
✗ Branch 5 not taken.
|
8976 | return std::isalnum(static_cast<unsigned char>(c)) || c == '_' || c == '-' || c == '.'; |
| 101 | }; | ||
| 102 | std::ranges::replace(token, ' ', '_'); | ||
| 103 |
2/2✓ Branch 0 taken 8976 times.
✓ Branch 1 taken 168 times.
|
9144 | for (char &ch : token) { |
| 104 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 8976 times.
|
8976 | if (!is_allowed(ch)) { |
| 105 | ✗ | ch = '_'; | |
| 106 | } | ||
| 107 | } | ||
| 108 | 168 | return token; | |
| 109 | } | ||
| 110 | |||
| 111 |
0/4✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
|
168 | class ScopedPerTestEnv { |
| 112 | public: | ||
| 113 | 168 | explicit ScopedPerTestEnv(const std::string &token) | |
| 114 |
4/10✗ Branch 0 not taken.
✓ Branch 1 taken 168 times.
✓ Branch 4 taken 168 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 168 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✓ Branch 10 taken 168 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
|
504 | : set_uid_("PPC_TEST_UID", token), set_tmp_("PPC_TEST_TMPDIR", CreateTmpDir(token)) {} |
| 115 | |||
| 116 | private: | ||
| 117 | 168 | static std::string CreateTmpDir(const std::string &token) { | |
| 118 | namespace fs = std::filesystem; | ||
| 119 | 24 | auto make_rank_suffix = []() -> std::string { | |
| 120 | // Derive rank from common MPI env vars without including MPI headers | ||
| 121 | 24 | constexpr std::array<std::string_view, 5> kRankVars = {"OMPI_COMM_WORLD_RANK", "PMI_RANK", "PMIX_RANK", | |
| 122 | "SLURM_PROCID", "MSMPI_RANK"}; | ||
| 123 |
1/2✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
|
24 | for (auto name : kRankVars) { |
| 124 |
2/4✓ Branch 1 taken 24 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 24 times.
✗ Branch 4 not taken.
|
24 | if (auto r = env::get<int>(name); r.has_value() && r.value() >= 0) { |
| 125 |
3/8✓ Branch 2 taken 24 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 24 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 24 times.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
|
72 | return std::string("_rank_") + std::to_string(r.value()); |
| 126 | } | ||
| 127 | } | ||
| 128 | return std::string{}; | ||
| 129 | }; | ||
| 130 |
2/2✓ Branch 1 taken 24 times.
✓ Branch 2 taken 144 times.
|
168 | const std::string rank_suffix = IsUnderMpirun() ? make_rank_suffix() : std::string{}; |
| 131 |
5/12✓ Branch 1 taken 168 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 168 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 168 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 168 times.
✗ Branch 11 not taken.
✓ Branch 15 taken 168 times.
✗ Branch 16 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
|
504 | const fs::path tmp = fs::temp_directory_path() / (std::string("ppc_test_") + token + rank_suffix); |
| 132 | std::error_code ec; | ||
| 133 |
1/2✓ Branch 1 taken 168 times.
✗ Branch 2 not taken.
|
168 | fs::create_directories(tmp, ec); |
| 134 | (void)ec; | ||
| 135 | 168 | return tmp.string(); | |
| 136 | 168 | } | |
| 137 | |||
| 138 | env::detail::set_scoped_environment_variable set_uid_; | ||
| 139 | env::detail::set_scoped_environment_variable set_tmp_; | ||
| 140 | }; | ||
| 141 | |||
| 142 | 168 | [[nodiscard]] inline std::string MakeCurrentGTestToken(std::string_view fallback_name) { | |
| 143 | 168 | const auto *unit = ::testing::UnitTest::GetInstance(); | |
| 144 |
1/2✓ Branch 0 taken 168 times.
✗ Branch 1 not taken.
|
168 | const auto *info = (unit != nullptr) ? unit->current_test_info() : nullptr; |
| 145 | 168 | std::ostringstream os; | |
| 146 |
1/2✓ Branch 0 taken 168 times.
✗ Branch 1 not taken.
|
168 | if (info != nullptr) { |
| 147 |
2/4✓ Branch 1 taken 168 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 168 times.
✗ Branch 5 not taken.
|
336 | os << info->test_suite_name() << "." << info->name(); |
| 148 | } else { | ||
| 149 | os << fallback_name; | ||
| 150 | } | ||
| 151 |
1/2✓ Branch 1 taken 168 times.
✗ Branch 2 not taken.
|
336 | return SanitizeToken(os.str()); |
| 152 | 168 | } | |
| 153 | |||
| 154 | 168 | inline ScopedPerTestEnv MakePerTestEnvForCurrentGTest(std::string_view fallback_name) { | |
| 155 |
1/2✓ Branch 2 taken 168 times.
✗ Branch 3 not taken.
|
336 | return ScopedPerTestEnv(MakeCurrentGTestToken(fallback_name)); |
| 156 | } | ||
| 157 | |||
| 158 | } // namespace test | ||
| 159 | |||
| 160 | } // namespace ppc::util | ||
| 161 |