blob: 4b3131d15b4352f9eaa9919a866040430dec0b6a [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#pragma once
#include <filesystem>
#include <format>
#include <fstream>
#include <random>
#include <string>
#include <string_view>
#include <vector>
#include <gtest/gtest.h>
namespace iceberg {
/// A base class for tests that need to create and manage temporary files.
/// Provides utilities for creating platform-independent temporary files
/// and ensures proper cleanup after tests run.
///
/// Usage Example:
/// ```
/// class MyTest : public test::TempFileTestBase {
/// protected:
/// void SetUp() override {
/// // Always call base class SetUp first
/// TempFileTestBase::SetUp();
///
/// // Create test resources
/// my_temp_file_ = CreateNewTempFilePath();
///
/// // Additional setup...
/// }
///
/// // Your test-specific members
/// std::string my_temp_file_;
/// };
///
/// TEST_F(MyTest, ExampleTest) {
/// // Use temporary files in your test
/// WriteContentToFile(my_temp_file_, "test content");
///
/// // Files will be automatically cleaned up in TearDown
/// }
/// ```
///
/// Notes:
/// - Always call TempFileTestBase::SetUp() in your derived class's SetUp()
/// - All files created using the provided methods will be automatically cleaned up
/// - You don't need to implement TearDown() unless you need additional cleanup
class TempFileTestBase : public ::testing::Test {
protected:
void SetUp() override {
// Base class setup is empty, but provided for derived classes to call
}
void TearDown() override {
// Clean up all temporary files and directories created during the test
for (const auto& path : created_temp_files_) {
std::error_code ec;
if (std::filesystem::is_directory(path, ec)) {
std::filesystem::remove_all(path, ec);
} else {
std::filesystem::remove(path, ec);
}
}
}
/// \brief Generates a unique temporary filepath that works across platforms
std::string GenerateUniqueTempFilePath() const {
std::filesystem::path temp_dir = std::filesystem::temp_directory_path();
std::string file_name =
std::format("iceberg_test_{}_{}.tmp", TestInfo(), GenerateRandomString(8));
return (temp_dir / file_name).string();
}
/// \brief Create a temporary filepath with the specified suffix/extension
std::string GenerateUniqueTempFilePathWithSuffix(const std::string& suffix) {
std::filesystem::path temp_dir = std::filesystem::temp_directory_path();
std::string file_name =
std::format("iceberg_test_{}_{}{}", TestInfo(), GenerateRandomString(8), suffix);
return (temp_dir / file_name).string();
}
/// \brief Helper to generate a random alphanumeric string for unique filenames
std::string GenerateRandomString(size_t length) const {
const std::string_view chars =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dist(0, static_cast<int>(chars.size() - 1));
std::string result;
result.reserve(length);
for (size_t i = 0; i < length; ++i) {
result += chars[dist(gen)];
}
return result;
}
/// \brief Get the test name for inclusion in the filename
std::string TestInfo() const {
if (const auto info = ::testing::UnitTest::GetInstance()->current_test_info(); info) {
std::string result = std::format("{}_{}", info->test_suite_name(), info->name());
// Replace slashes (from parameterized tests) with underscores to avoid path issues
for (auto& c : result) {
if (c == '/') {
c = '_';
}
}
return result;
}
return "unknown_test";
}
/// \brief Creates a new temporary filepath and registers it for cleanup
std::string CreateNewTempFilePath() {
std::string filepath = GenerateUniqueTempFilePath();
created_temp_files_.push_back(filepath);
return filepath;
}
/// \brief Create a temporary filepath with the specified suffix and registers it for
/// cleanup
std::string CreateNewTempFilePathWithSuffix(const std::string& suffix) {
std::string filepath = GenerateUniqueTempFilePathWithSuffix(suffix);
created_temp_files_.push_back(filepath);
return filepath;
}
/// \brief Creates a temporary directory and registers it for cleanup
std::string CreateTempDirectory() {
std::filesystem::path temp_dir = std::filesystem::temp_directory_path();
std::string dir_name =
std::format("iceberg_test_dir_{}_{}", TestInfo(), GenerateRandomString(8));
std::filesystem::path dir_path = temp_dir / dir_name;
std::error_code ec;
std::filesystem::create_directory(dir_path, ec);
if (ec) {
throw std::runtime_error(
std::format("Failed to create temporary directory: {}", ec.message()));
}
created_temp_files_.push_back(dir_path.string());
return dir_path.string();
}
/// \brief Creates a file with the given content at the specified path
void WriteContentToFile(const std::string& path, const std::string& content) {
std::ofstream file(path, std::ios::binary);
if (!file) {
throw std::runtime_error(std::format("Failed to open file for writing: {}", path));
}
file.write(content.data(), content.size());
if (!file) {
throw std::runtime_error(std::format("Failed to write to file: {}", path));
}
}
/// \brief Creates a new temporary file with the given content
std::string CreateTempFileWithContent(const std::string& content) {
std::string path = CreateNewTempFilePath();
WriteContentToFile(path, content);
return path;
}
std::vector<std::string> created_temp_files_;
};
} // namespace iceberg