| /* |
| * 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 |