Tests: Add tests for timestamp-based backups

This commit is contained in:
Chris Hennes
2025-07-19 22:11:19 -05:00
committed by Kacper Donat
parent 8173b11104
commit 26c6aa934d
2 changed files with 328 additions and 1 deletions

View File

@@ -46,6 +46,8 @@ macro(CompilerChecksAndSetups)
else()
set(CMAKE_CXX_STANDARD 20)
endif()
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# Log the compiler and version
message(STATUS "Compiler: ${CMAKE_CXX_COMPILER_ID}, version: ${CMAKE_CXX_COMPILER_VERSION}")

View File

@@ -29,10 +29,22 @@
#include <App/BackupPolicy.h>
#include <filesystem>
#include <array>
#include <fstream>
#include <random>
#include <regex>
#include <string>
#if defined(__cpp_lib_chrono) && __cpp_lib_chrono >= 201907L && defined(_LIBCPP_VERSION) \
&& _LIBCPP_VERSION >= 13000
// Apple's clang compiler did not support timezones fully until a quite recent version:
// before removing this preprocessor check, verify that it compiles on our oldest-supported
// macOS version.
#define CAN_USE_CHRONO_AND_FORMAT
#include <chrono>
#include <format>
#endif
class BackupPolicyTest: public ::testing::Test
{
@@ -79,7 +91,7 @@ protected:
}
private:
protected:
std::string randomString(size_t length)
{
static constexpr std::string_view chars = "0123456789"
@@ -100,6 +112,39 @@ private:
return result;
}
std::string filenameFromDateFormatString(const std::string& fmt)
{
#if CAN_USE_CHRONO_AND_FORMAT
std::chrono::zoned_time local_time {std::chrono::current_zone(),
std::chrono::system_clock::now()};
std::string fmt_str = "{:" + fmt + "}";
std::string result = std::vformat(fmt_str, std::make_format_args(local_time));
#else
std::time_t now = std::time(nullptr);
std::tm local_tm {};
#if defined(_WIN32)
localtime_s(&local_tm, &now); // Windows
#else
localtime_r(&now, &local_tm); // POSIX
#endif
constexpr size_t bufferLength = 128;
std::array<char, bufferLength> buffer {};
size_t bytes = std::strftime(buffer.data(), bufferLength, fmt.c_str(), &local_tm);
if (bytes == 0) {
throw std::runtime_error("failed to format time");
}
std::string result {buffer.data()};
#endif
return result;
}
std::filesystem::path getTempPath()
{
return _tempDir;
}
private:
App::BackupPolicy _policy;
std::filesystem::path _tempDir;
};
@@ -225,3 +270,283 @@ TEST_F(BackupPolicyTest, StandardWithFCBakSet)
// Assert
EXPECT_TRUE(std::filesystem::exists(target.string() + "1")); // No FCBak extension for Standard
}
TEST_F(BackupPolicyTest, TimestampSourceDoesNotExist)
{
// Arrange
setPolicyTerms(App::BackupPolicy::Policy::TimeStamp, 1, false, "%Y-%m-%d_%H-%M-%S");
// Act & Assert
EXPECT_THROW(apply("nonexistent.fcstd", "backup.fcstd"), Base::FileException);
}
TEST_F(BackupPolicyTest, TimestampNoSourceGiven)
{
// Arrange
setPolicyTerms(App::BackupPolicy::Policy::TimeStamp, 1, false, "%Y-%m-%d_%H-%M-%S");
auto target = createTempFile("target.fcstd");
// Act & Assert
EXPECT_THROW(apply("nonexistent.fcstd", target.string()), Base::FileException);
}
TEST_F(BackupPolicyTest, TimestampNoTargetGiven)
{
// Arrange
setPolicyTerms(App::BackupPolicy::Policy::TimeStamp, 1, false, "%Y-%m-%d_%H-%M-%S");
auto source = createTempFile("source.fcstd");
// Act & Assert
EXPECT_THROW(apply(source.string(), ""), Base::FileException);
}
TEST_F(BackupPolicyTest, TimestampWithZeroFilesDeletesExisting)
{
// Arrange
setPolicyTerms(App::BackupPolicy::Policy::TimeStamp, 0, false, "%Y-%m-%d_%H-%M-%S");
auto source = createTempFile("source.fcstd");
auto target = createTempFile("target.fcstd");
// Act
apply(source.string(), target.string());
// Assert
GTEST_SKIP(); // Can't test on a real filesystem, too much caching for reliable results
EXPECT_FALSE(std::filesystem::exists(target));
}
TEST_F(BackupPolicyTest, TimestampWithOneFileAndNoneExistingNotFCBakCreatesNumberedFile)
{
// Arrange
setPolicyTerms(App::BackupPolicy::Policy::TimeStamp, 1, false, "%Y-%m-%d");
auto source = createTempFile("source.fcstd");
auto target = createTempFile("target.fcstd");
// Act
apply(source.string(), target.string());
// Assert
// Without the .FCBak extension, the date stuff is completely ignored, even if the policy is set
// to "Timestamp"
EXPECT_TRUE(std::filesystem::exists(target.string() + "1"));
}
TEST_F(BackupPolicyTest, TimestampSourceHasNoExtension)
{
// Arrange
setPolicyTerms(App::BackupPolicy::Policy::TimeStamp, 1, true, "%Y-%m-%d");
auto source = createTempFile("source");
auto target = createTempFile("target.fcstd");
// Act
apply(source.string(), target.string());
// Assert
auto expected = "target." + filenameFromDateFormatString("%Y-%m-%d") + ".FCBak";
EXPECT_TRUE(std::filesystem::exists(getTempPath() / expected));
}
TEST_F(BackupPolicyTest, TimestampTargetHasNoExtension)
{
// Arrange
setPolicyTerms(App::BackupPolicy::Policy::TimeStamp, 1, true, "%Y-%m-%d");
auto source = createTempFile("source.fcstd");
auto target = createTempFile("target");
// Act
apply(source.string(), target.string());
// Assert
auto expected = "target." + filenameFromDateFormatString("%Y-%m-%d") + ".FCBak";
EXPECT_TRUE(std::filesystem::exists(getTempPath() / expected));
}
TEST_F(BackupPolicyTest, TimestampWithOneFileAndNoneExistingFCBakCreatesDatedFile)
{
// Arrange
setPolicyTerms(App::BackupPolicy::Policy::TimeStamp, 1, true, "%Y-%m-%d");
auto source = createTempFile("source.fcstd");
auto target = createTempFile("target.fcstd");
// Act
apply(source.string(), target.string());
// Assert
auto expected = "target." + filenameFromDateFormatString("%Y-%m-%d") + ".FCBak";
EXPECT_TRUE(std::filesystem::exists(getTempPath() / expected));
}
TEST_F(BackupPolicyTest, TimestampReplacesDotsWithDashes)
{
// Arrange
setPolicyTerms(App::BackupPolicy::Policy::TimeStamp, 1, true, "%Y.%m.%d");
auto source = createTempFile("source.fcstd");
auto target = createTempFile("target.fcstd");
// Act
apply(source.string(), target.string());
// Assert
auto expected = "target." + filenameFromDateFormatString("%Y-%m-%d") + ".FCBak";
EXPECT_TRUE(std::filesystem::exists(getTempPath() / expected));
}
TEST_F(BackupPolicyTest, DISABLED_TimestampWithInvalidFormatStringThrows)
{
// THIS TEST IS DISABLED BECAUSE THE CURRENT CODE DOES NOT CORRECTLY HANDLE INVALID FORMAT
// OPERATIONS, AND CRASHES WHEN GIVEN ONE. FIXME.
// Arrange
setPolicyTerms(App::BackupPolicy::Policy::TimeStamp, 1, true, "%Q-%W-%E");
auto source = createTempFile("source.fcstd");
auto target = createTempFile("target.fcstd");
// Act and Assert
EXPECT_THROW(apply(source.string(), target.string()), Base::FileException);
}
TEST_F(BackupPolicyTest, DISABLED_TimestampWithAbsurdlyLongFormatStringThrows)
{
// THIS TEST IS DISABLED BECAUSE THE CURRENT CODE DOES NOT CORRECTLY HANDLE OVER-LENGTH FORMAT
// OPERATIONS, AND GENERATES AN INVALID FILENAME. FIXME.
// Arrange
setPolicyTerms(App::BackupPolicy::Policy::TimeStamp,
1,
true,
"%A, %B %d, %Y at %H:%M:%S %Z (Day %j of the year, Week %U/%W) — This is a "
"verbose date string for demonstration purposes.");
auto source = createTempFile("source.fcstd");
auto target = createTempFile("target.fcstd");
// Act and Assert
EXPECT_THROW(apply(source.string(), target.string()), Base::FileException);
}
TEST_F(BackupPolicyTest, TimestampDetectsOldBackupFormat)
{
// Arrange
setPolicyTerms(App::BackupPolicy::Policy::TimeStamp, 1, true, "%Y-%m-%d");
auto source = createTempFile("source.fcstd");
auto target = createTempFile("target.fcstd");
auto backup = createTempFile("target.fcstd12345");
// Act
apply(source.string(), target.string());
// Assert
bool check1 = std::filesystem::exists(
getTempPath() / ("target." + filenameFromDateFormatString("%Y-%m-%d") + ".FCBak"));
bool check2 = std::filesystem::exists(backup);
EXPECT_NE(check1, check2);
}
TEST_F(BackupPolicyTest, TimestampDetectsOldBackupFormatIgnoresOther)
{
// Arrange
setPolicyTerms(App::BackupPolicy::Policy::TimeStamp, 1, true, "%Y-%m-%d");
auto source = createTempFile("source.fcstd");
auto target = createTempFile("target.fcstd");
auto backup = createTempFile("target.fcstd12345");
auto weird = createTempFile("target.fcstd12345abc");
// Act
apply(source.string(), target.string());
// Assert
bool check1 = std::filesystem::exists(
getTempPath() / ("target." + filenameFromDateFormatString("%Y-%m-%d") + ".FCBak"));
bool check2 = std::filesystem::exists(backup);
EXPECT_NE(check1, check2);
EXPECT_TRUE(std::filesystem::exists(weird));
}
TEST_F(BackupPolicyTest, TimestampDetectsAndRetainsOldBackupWhenAllowed)
{
// Arrange
setPolicyTerms(App::BackupPolicy::Policy::TimeStamp, 2, true, "%Y-%m-%d");
auto source = createTempFile("source.fcstd");
auto target = createTempFile("target.fcstd");
auto backup = createTempFile("target.fcstd12345");
// Act
apply(source.string(), target.string());
// Assert
EXPECT_TRUE(std::filesystem::exists(
getTempPath() / ("target." + filenameFromDateFormatString("%Y-%m-%d") + ".FCBak")));
EXPECT_TRUE(std::filesystem::exists(backup));
}
TEST_F(BackupPolicyTest, TimestampFormatStringEndsWithSpace)
{
// Arrange
setPolicyTerms(App::BackupPolicy::Policy::TimeStamp, 1, true, "%Y-%m-%d ");
auto source = createTempFile("source.fcstd");
auto target = createTempFile("target.fcstd");
// Act
apply(source.string(), target.string());
// Assert (the space is stripped, and an index is added)
EXPECT_TRUE(std::filesystem::exists(
getTempPath() / ("target." + filenameFromDateFormatString("%Y-%m-%d") + "1.FCBak")));
}
TEST_F(BackupPolicyTest, TimestampFormatStringEndsWithDash)
{
// Arrange
setPolicyTerms(App::BackupPolicy::Policy::TimeStamp, 1, true, "%Y-%m-%d-");
auto source = createTempFile("source.fcstd");
auto target = createTempFile("target.fcstd");
// Act
apply(source.string(), target.string());
// Assert (the dash is left, and an index is added)
EXPECT_TRUE(std::filesystem::exists(
getTempPath() / ("target." + filenameFromDateFormatString("%Y-%m-%d") + "-1.FCBak")));
}
TEST_F(BackupPolicyTest, TimestampFormatFileAlreadyExists)
{
// Arrange
setPolicyTerms(App::BackupPolicy::Policy::TimeStamp, 2, true, "%Y-%m-%d");
auto source = createTempFile("source.fcstd");
auto target = createTempFile("target.fcstd");
auto backup = createTempFile("target." + filenameFromDateFormatString("%Y-%m-%d") + ".FCBak");
// Act
apply(source.string(), target.string());
// Assert (An index is appended)
EXPECT_TRUE(std::filesystem::exists(backup));
EXPECT_TRUE(std::filesystem::exists(
getTempPath() / ("target." + filenameFromDateFormatString("%Y-%m-%d") + "-1.FCBak")));
}
TEST_F(BackupPolicyTest, TimestampFormatFileAlreadyExistsMultipleTimes)
{
// Arrange
setPolicyTerms(App::BackupPolicy::Policy::TimeStamp, 5, true, "%Y-%m-%d");
auto source = createTempFile("source.fcstd");
auto target = createTempFile("target.fcstd");
auto backup = createTempFile("target." + filenameFromDateFormatString("%Y-%m-%d") + ".FCBak");
auto backup1 =
createTempFile("target." + filenameFromDateFormatString("%Y-%m-%d") + "-1.FCBak");
auto backup2 =
createTempFile("target." + filenameFromDateFormatString("%Y-%m-%d") + "-2.FCBak");
auto backup3 =
createTempFile("target." + filenameFromDateFormatString("%Y-%m-%d") + "-3.FCBak");
// Act
apply(source.string(), target.string());
// Assert (An index is appended)
EXPECT_TRUE(std::filesystem::exists(backup));
EXPECT_TRUE(std::filesystem::exists(backup1));
EXPECT_TRUE(std::filesystem::exists(backup2));
EXPECT_TRUE(std::filesystem::exists(backup3));
EXPECT_TRUE(std::filesystem::exists(
getTempPath() / ("target." + filenameFromDateFormatString("%Y-%m-%d") + "-4.FCBak")));
}