File output_dir.hpp
File List > lib > testing > output_dir.hpp
Go to the documentation of this file
#pragma once
#include <gtest/gtest.h>
#include <algorithm>
#include <atomic>
#include <cstdint>
#include <filesystem>
#include <random>
#include <sstream>
#include <string>
#include <string_view>
#ifdef _WIN32
#include <process.h>
#define BLAZE_TEST_GETPID _getpid
#else
#include <unistd.h>
#define BLAZE_TEST_GETPID getpid
#endif
namespace blaze::test {
// Build a filesystem-safe version of `s` by replacing characters that are
// problematic in paths (slashes, colons, etc.) with underscores. This matters
// for parameterized test names like "E2E_New/E2ETerrainTest.ProcessTerrain/..."
// which contain '/'.
inline std::string sanitize_for_path(std::string_view s) {
std::string out(s);
for (char& c : out) {
if (c == '/' || c == '\\' || c == ':' || c == ' ' || c == '<' || c == '>' || c == '|' ||
c == '"' || c == '?' || c == '*') {
c = '_';
}
}
return out;
}
// Returns a path under the system temp directory that is unique for this test
// run. Uniqueness is ensured by combining:
// - an optional caller-supplied label (for readability),
// - the current gtest test suite + test name if running inside a gtest case,
// - the process id (so concurrent `ctest -j` or CI shards don't collide),
// - a process-local monotonic counter (so multiple dirs within one test
// don't reuse the same name),
// - a random suffix (belt-and-braces against exotic schedulers).
//
// The directory is NOT created; callers remain responsible for create/remove.
inline std::filesystem::path unique_test_output_dir(std::string_view label = {}) {
const ::testing::TestInfo* info = ::testing::UnitTest::GetInstance()->current_test_info();
std::ostringstream oss;
oss << "blaze_test";
if (!label.empty()) {
oss << "_" << sanitize_for_path(label);
}
if (info != nullptr) {
oss << "_" << sanitize_for_path(info->test_suite_name()) << "."
<< sanitize_for_path(info->name());
}
oss << "_pid" << static_cast<unsigned long long>(BLAZE_TEST_GETPID());
static std::atomic<std::uint64_t> counter{0};
oss << "_" << counter.fetch_add(1, std::memory_order_relaxed);
// Random suffix: seeded once per process from std::random_device so two
// processes with the same PID reuse (e.g. after PID wrap) still diverge.
static thread_local std::mt19937_64 rng{[] {
std::random_device rd;
return (static_cast<std::uint64_t>(rd()) << 32) | rd();
}()};
oss << "_" << std::hex << rng();
return std::filesystem::temp_directory_path() / oss.str();
}
// Variant that appends `extension` (e.g. ".gpkg") to the unique path. Useful
// for tests that write a single file instead of a directory tree.
inline std::filesystem::path unique_test_output_path(std::string_view label,
std::string_view extension) {
std::filesystem::path p = unique_test_output_dir(label);
if (!extension.empty()) {
p += std::string(extension);
}
return p;
}
} // namespace blaze::test
#undef BLAZE_TEST_GETPID