1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
#include "vfs/inodetable.h"
#include "vfs/overwritemanager.h"
#include <fcntl.h>
#include <unistd.h>
#include <gtest/gtest.h>
#include <filesystem>
#include <fstream>
#include <string>
namespace fs = std::filesystem;
namespace
{
class TempRoot
{
public:
TempRoot()
{
char tmpl[] = "/tmp/fluorine-vfs-ops-XXXXXX";
const char* d = mkdtemp(tmpl);
if (d != nullptr) {
m_path = d;
}
}
~TempRoot()
{
if (!m_path.empty()) {
std::error_code ec;
fs::remove_all(m_path, ec);
}
}
const fs::path& path() const { return m_path; }
private:
fs::path m_path;
};
void writeText(const fs::path& path, const std::string& text)
{
fs::create_directories(path.parent_path());
std::ofstream out(path, std::ios::binary | std::ios::trunc);
ASSERT_TRUE(out.is_open());
out << text;
}
std::string readText(const fs::path& path)
{
std::ifstream in(path, std::ios::binary);
return std::string(std::istreambuf_iterator<char>(in),
std::istreambuf_iterator<char>());
}
} // namespace
TEST(VfsFileOps, CopyOnWriteFromFdCanCopyBackingSourceToDifferentDestination)
{
TempRoot tmp;
ASSERT_FALSE(tmp.path().empty());
const fs::path backing = tmp.path() / "Data";
const fs::path staging = tmp.path() / "staging";
const fs::path overwrite = tmp.path() / "overwrite";
writeText(backing / "Plugin.esp.save_2026_05_27", "saved plugin");
writeText(backing / "Plugin.esp", "old plugin");
const int dirFd = open(backing.c_str(), O_RDONLY | O_DIRECTORY);
ASSERT_GE(dirFd, 0);
OverwriteManager overwriteManager(staging.string(), overwrite.string());
const std::string staged = overwriteManager.copyOnWriteFromFd(
dirFd, "Plugin.esp.save_2026_05_27", "Plugin.esp");
close(dirFd);
EXPECT_EQ(fs::path(staged), staging / "Plugin.esp");
EXPECT_EQ(readText(staging / "Plugin.esp"), "saved plugin");
}
TEST(VfsFileOps, CreateFileReturnsWritableStagingHandle)
{
TempRoot tmp;
ASSERT_FALSE(tmp.path().empty());
const fs::path staging = tmp.path() / "staging";
const fs::path overwrite = tmp.path() / "overwrite";
OverwriteManager overwriteManager(staging.string(), overwrite.string());
std::string realPath;
const int fd = overwriteManager.createFile("ShaderCache/Lighting/test.pso",
0600, &realPath);
ASSERT_GE(fd, 0);
const char payload[] = "shader";
ASSERT_EQ(write(fd, payload, sizeof(payload) - 1), ssize_t(sizeof(payload) - 1));
close(fd);
EXPECT_EQ(fs::path(realPath), staging / "ShaderCache/Lighting/test.pso");
EXPECT_EQ(readText(realPath), "shader");
}
TEST(VfsFileOps, InodeRenameMovesDescendants)
{
InodeTable table;
const uint64_t parent = table.getOrCreate("meshes/actors");
const uint64_t child = table.getOrCreate("meshes/actors/character/body.nif");
table.rename("meshes/actors", "meshes/actors_backup");
EXPECT_EQ(table.get("meshes/actors"), 0u);
EXPECT_EQ(table.get("meshes/actors_backup"), parent);
EXPECT_EQ(table.get("meshes/actors_backup/character/body.nif"), child);
EXPECT_EQ(table.getPath(child), "meshes/actors_backup/character/body.nif");
}
|