1#include "test.h"
2
3#include <base/fs.h>
4#include <base/io.h>
5#include <base/str.h>
6
7#include <gtest/gtest.h>
8
9TEST(Filesystem, Filename)
10{
11 EXPECT_STREQ(fs_filename(""), "");
12 EXPECT_STREQ(fs_filename("/"), "");
13 EXPECT_STREQ(fs_filename("\\"), "");
14 EXPECT_STREQ(fs_filename("a"), "a");
15 EXPECT_STREQ(fs_filename("abc"), "abc");
16 EXPECT_STREQ(fs_filename("a/b"), "b");
17 EXPECT_STREQ(fs_filename("a/b/c"), "c");
18 EXPECT_STREQ(fs_filename("/a/b/c"), "c");
19 EXPECT_STREQ(fs_filename("aaaaa/bbbb/ccc"), "ccc");
20 EXPECT_STREQ(fs_filename("aaaaa\\bbbb\\ccc"), "ccc");
21 EXPECT_STREQ(fs_filename("aaaaa/bbbb\\ccc"), "ccc");
22 EXPECT_STREQ(fs_filename("aaaaa\\bbbb/ccc"), "ccc");
23 EXPECT_STREQ(fs_filename("aa.aaa/bbbb/ccc"), "ccc");
24 EXPECT_STREQ(fs_filename("aaaaa/bb.bb/ccc"), "ccc");
25 EXPECT_STREQ(fs_filename("aa.aaa/bb.bb/ccc"), "ccc");
26 EXPECT_STREQ(fs_filename("aa.aaa\\bbbb\\ccc"), "ccc");
27 EXPECT_STREQ(fs_filename("aaaaa\\bb.bb\\ccc"), "ccc");
28 EXPECT_STREQ(fs_filename("aa.aaa\\bb.bb\\ccc"), "ccc");
29}
30
31TEST(Filesystem, SplitFileExtension)
32{
33 char aName[IO_MAX_PATH_LENGTH];
34 char aExt[IO_MAX_PATH_LENGTH];
35
36 fs_split_file_extension(filename: "", name: aName, name_size: sizeof(aName), extension: aExt, extension_size: sizeof(aExt));
37 EXPECT_STREQ(aName, "");
38 EXPECT_STREQ(aExt, "");
39
40 fs_split_file_extension(filename: "name.ext", name: aName, name_size: sizeof(aName), extension: aExt, extension_size: sizeof(aExt));
41 EXPECT_STREQ(aName, "name");
42 EXPECT_STREQ(aExt, "ext");
43
44 fs_split_file_extension(filename: "name.ext", name: aName, name_size: sizeof(aName)); // extension parameter is optional
45 EXPECT_STREQ(aName, "name");
46
47 fs_split_file_extension(filename: "name.ext", name: nullptr, name_size: 0, extension: aExt, extension_size: sizeof(aExt)); // name parameter is optional
48 EXPECT_STREQ(aExt, "ext");
49
50 fs_split_file_extension(filename: "archive.tar.gz", name: aName, name_size: sizeof(aName), extension: aExt, extension_size: sizeof(aExt));
51 EXPECT_STREQ(aName, "archive.tar");
52 EXPECT_STREQ(aExt, "gz");
53
54 fs_split_file_extension(filename: "no_dot", name: aName, name_size: sizeof(aName), extension: aExt, extension_size: sizeof(aExt));
55 EXPECT_STREQ(aName, "no_dot");
56 EXPECT_STREQ(aExt, "");
57
58 fs_split_file_extension(filename: "no_dot", name: aName, name_size: sizeof(aName)); // extension parameter is optional
59 EXPECT_STREQ(aName, "no_dot");
60
61 fs_split_file_extension(filename: "no_dot", name: nullptr, name_size: 0, extension: aExt, extension_size: sizeof(aExt)); // name parameter is optional
62 EXPECT_STREQ(aExt, "");
63
64 fs_split_file_extension(filename: ".dot_first", name: aName, name_size: sizeof(aName), extension: aExt, extension_size: sizeof(aExt));
65 EXPECT_STREQ(aName, ".dot_first");
66 EXPECT_STREQ(aExt, "");
67
68 fs_split_file_extension(filename: ".dot_first", name: aName, name_size: sizeof(aName)); // extension parameter is optional
69 EXPECT_STREQ(aName, ".dot_first");
70
71 fs_split_file_extension(filename: ".dot_first", name: nullptr, name_size: 0, extension: aExt, extension_size: sizeof(aExt)); // name parameter is optional
72 EXPECT_STREQ(aExt, "");
73}
74
75static void TestNormalizePath(const char *pInput, const char *pExpectedOutput)
76{
77 char aNormalized[IO_MAX_PATH_LENGTH];
78 str_copy(dst&: aNormalized, src: pInput);
79 fs_normalize_path(path: aNormalized);
80 EXPECT_STREQ(aNormalized, pExpectedOutput);
81}
82
83TEST(Filesystem, NormalizePath)
84{
85 TestNormalizePath(pInput: "", pExpectedOutput: "");
86 TestNormalizePath(pInput: "/", pExpectedOutput: "/");
87 TestNormalizePath(pInput: "\\", pExpectedOutput: "/");
88 TestNormalizePath(pInput: "//", pExpectedOutput: "/");
89 TestNormalizePath(pInput: "/////", pExpectedOutput: "/");
90 TestNormalizePath(pInput: "\\\\\\\\\\", pExpectedOutput: "/");
91 TestNormalizePath(pInput: "/a/b/c", pExpectedOutput: "/a/b/c");
92 TestNormalizePath(pInput: "\\a\\b\\c", pExpectedOutput: "/a/b/c");
93 TestNormalizePath(pInput: "C:\\Users", pExpectedOutput: "C:/Users");
94 TestNormalizePath(pInput: "C:\\", pExpectedOutput: "C:");
95}
96
97TEST(Filesystem, StoragePath)
98{
99 char aStoragePath[IO_MAX_PATH_LENGTH];
100 ASSERT_FALSE(fs_storage_path("TestAppName", aStoragePath, sizeof(aStoragePath)));
101 EXPECT_FALSE(fs_is_relative_path(aStoragePath));
102 EXPECT_TRUE(str_endswith_nocase(aStoragePath, "/TestAppName"));
103}
104
105TEST(Filesystem, ExecutablePath)
106{
107 char aExecutablePath[IO_MAX_PATH_LENGTH];
108 ASSERT_FALSE(fs_executable_path(aExecutablePath, sizeof(aExecutablePath)));
109 EXPECT_TRUE(fs_is_file(aExecutablePath));
110 fs_parent_dir(path: aExecutablePath);
111 EXPECT_FALSE(fs_is_relative_path(aExecutablePath));
112}
113
114TEST(Filesystem, CreateCloseDelete)
115{
116 CTestInfo Info;
117
118 EXPECT_FALSE(fs_is_file(Info.m_aFilename));
119 IOHANDLE File = io_open(filename: Info.m_aFilename, flags: IOFLAG_WRITE);
120 ASSERT_TRUE(File);
121 EXPECT_FALSE(io_close(File));
122 EXPECT_TRUE(fs_is_file(Info.m_aFilename));
123 EXPECT_FALSE(fs_remove(Info.m_aFilename));
124 EXPECT_FALSE(fs_is_file(Info.m_aFilename));
125}
126
127TEST(Filesystem, CreateDeleteDirectory)
128{
129 CTestInfo Info;
130 char aFilename[IO_MAX_PATH_LENGTH];
131 str_format(buffer: aFilename, buffer_size: sizeof(aFilename), format: "%s/test.txt", Info.m_aFilename);
132
133 EXPECT_FALSE(fs_is_dir(Info.m_aFilename));
134 EXPECT_FALSE(fs_makedir(Info.m_aFilename));
135 EXPECT_TRUE(fs_is_dir(Info.m_aFilename));
136
137 IOHANDLE File = io_open(filename: aFilename, flags: IOFLAG_WRITE);
138 ASSERT_TRUE(File);
139 EXPECT_FALSE(io_close(File));
140
141 // Directory removal fails if there are any files left in the directory.
142 EXPECT_TRUE(fs_removedir(Info.m_aFilename));
143 EXPECT_TRUE(fs_is_dir(Info.m_aFilename));
144
145 EXPECT_FALSE(fs_remove(aFilename));
146 EXPECT_FALSE(fs_removedir(Info.m_aFilename));
147 EXPECT_FALSE(fs_is_dir(Info.m_aFilename));
148}
149
150TEST(Filesystem, CantDeleteDirectoryWithRemove)
151{
152 CTestInfo Info;
153 EXPECT_FALSE(fs_makedir(Info.m_aFilename));
154 EXPECT_TRUE(fs_remove(Info.m_aFilename)); // Cannot remove directory with file removal function.
155 EXPECT_FALSE(fs_removedir(Info.m_aFilename));
156}
157
158TEST(Filesystem, CantDeleteFileWithRemoveDirectory)
159{
160 CTestInfo Info;
161 IOHANDLE File = io_open(filename: Info.m_aFilename, flags: IOFLAG_WRITE);
162 ASSERT_TRUE(File);
163 EXPECT_FALSE(io_close(File));
164 EXPECT_TRUE(fs_removedir(Info.m_aFilename)); // Cannot remove file with directory removal function.
165 EXPECT_FALSE(fs_remove(Info.m_aFilename));
166}
167
168TEST(Filesystem, DeleteNonexistentFile)
169{
170 CTestInfo Info;
171
172 EXPECT_FALSE(fs_is_file(Info.m_aFilename));
173 EXPECT_FALSE(fs_remove(Info.m_aFilename)); // Can delete a file that does not exist.
174}
175
176TEST(Filesystem, DeleteNonexistentDirectory)
177{
178 CTestInfo Info;
179
180 EXPECT_FALSE(fs_is_dir(Info.m_aFilename));
181 EXPECT_FALSE(fs_removedir(Info.m_aFilename)); // Can delete a folder that does not exist.
182}
183
184TEST(Filesystem, DeleteOpenFile)
185{
186 CTestInfo Info;
187
188 IOHANDLE File = io_open(filename: Info.m_aFilename, flags: IOFLAG_WRITE);
189 ASSERT_TRUE(File);
190
191 EXPECT_TRUE(fs_is_file(Info.m_aFilename));
192 EXPECT_FALSE(fs_remove(Info.m_aFilename)); // Can delete a file that has open handle.
193 EXPECT_FALSE(fs_is_file(Info.m_aFilename)); // File should be gone immediately even before the last file handle is closed.
194
195 EXPECT_FALSE(io_close(File));
196}
197
198TEST(Filesystem, DeleteOpenFileMultipleHandles)
199{
200 CTestInfo Info;
201
202 IOHANDLE FileWrite1 = io_open(filename: Info.m_aFilename, flags: IOFLAG_WRITE);
203 ASSERT_TRUE(FileWrite1);
204
205 IOHANDLE FileRead1 = io_open(filename: Info.m_aFilename, flags: IOFLAG_READ);
206 ASSERT_TRUE(FileRead1);
207
208 IOHANDLE FileWrite2 = io_open(filename: Info.m_aFilename, flags: IOFLAG_WRITE);
209 ASSERT_TRUE(FileWrite2);
210
211 IOHANDLE FileRead2 = io_open(filename: Info.m_aFilename, flags: IOFLAG_READ);
212 ASSERT_TRUE(FileRead2);
213
214 EXPECT_TRUE(fs_is_file(Info.m_aFilename));
215 EXPECT_FALSE(fs_remove(Info.m_aFilename)); // Can delete a file that has multiple open handles.
216 EXPECT_FALSE(fs_is_file(Info.m_aFilename)); // File should be gone immediately even before the last file handle is closed.
217
218 EXPECT_FALSE(io_close(FileWrite1));
219 EXPECT_FALSE(io_close(FileRead1));
220 EXPECT_FALSE(io_close(FileWrite2));
221 EXPECT_FALSE(io_close(FileRead2));
222}
223
224TEST(Filesystem, RenameFile)
225{
226 char aNewFilename[IO_MAX_PATH_LENGTH];
227 CTestInfo Info;
228 Info.Filename(pBuffer: aNewFilename, BufferLength: sizeof(aNewFilename), pSuffix: ".renamed");
229
230 IOHANDLE FileWrite = io_open(filename: Info.m_aFilename, flags: IOFLAG_WRITE);
231 ASSERT_TRUE(FileWrite);
232 EXPECT_FALSE(io_close(FileWrite));
233
234 EXPECT_TRUE(fs_is_file(Info.m_aFilename));
235 EXPECT_FALSE(fs_rename(Info.m_aFilename, aNewFilename));
236 EXPECT_FALSE(fs_is_file(Info.m_aFilename));
237 EXPECT_TRUE(fs_is_file(aNewFilename));
238
239 EXPECT_FALSE(fs_remove(aNewFilename));
240}
241
242TEST(Filesystem, RenameFolder)
243{
244 char aNewFilename[IO_MAX_PATH_LENGTH];
245 CTestInfo Info;
246 Info.Filename(pBuffer: aNewFilename, BufferLength: sizeof(aNewFilename), pSuffix: ".renamed");
247
248 EXPECT_FALSE(fs_makedir(Info.m_aFilename));
249
250 EXPECT_TRUE(fs_is_dir(Info.m_aFilename));
251 EXPECT_FALSE(fs_rename(Info.m_aFilename, aNewFilename));
252 EXPECT_FALSE(fs_is_dir(Info.m_aFilename));
253 EXPECT_TRUE(fs_is_dir(aNewFilename));
254
255 EXPECT_FALSE(fs_removedir(aNewFilename));
256}
257
258TEST(Filesystem, RenameOpenFileSource)
259{
260 char aNewFilename[IO_MAX_PATH_LENGTH];
261 CTestInfo Info;
262 Info.Filename(pBuffer: aNewFilename, BufferLength: sizeof(aNewFilename), pSuffix: ".renamed");
263
264 IOHANDLE FileWrite = io_open(filename: Info.m_aFilename, flags: IOFLAG_WRITE);
265 ASSERT_TRUE(FileWrite);
266
267 EXPECT_TRUE(fs_is_file(Info.m_aFilename));
268 EXPECT_FALSE(fs_rename(Info.m_aFilename, aNewFilename)); // Can rename a file that has open handle.
269 EXPECT_FALSE(fs_is_file(Info.m_aFilename)); // Rename should take effect immediately.
270 EXPECT_TRUE(fs_is_file(aNewFilename));
271
272 EXPECT_FALSE(io_close(FileWrite));
273
274 EXPECT_FALSE(fs_remove(aNewFilename));
275}
276
277TEST(Filesystem, RenameOpenFileSourceMultipleHandles)
278{
279 char aNewFilename[IO_MAX_PATH_LENGTH];
280 CTestInfo Info;
281 Info.Filename(pBuffer: aNewFilename, BufferLength: sizeof(aNewFilename), pSuffix: ".renamed");
282
283 IOHANDLE FileWrite1 = io_open(filename: Info.m_aFilename, flags: IOFLAG_WRITE);
284 ASSERT_TRUE(FileWrite1);
285
286 IOHANDLE FileRead1 = io_open(filename: Info.m_aFilename, flags: IOFLAG_READ);
287 ASSERT_TRUE(FileRead1);
288
289 IOHANDLE FileWrite2 = io_open(filename: Info.m_aFilename, flags: IOFLAG_WRITE);
290 ASSERT_TRUE(FileWrite2);
291
292 IOHANDLE FileRead2 = io_open(filename: Info.m_aFilename, flags: IOFLAG_READ);
293 ASSERT_TRUE(FileRead2);
294
295 EXPECT_TRUE(fs_is_file(Info.m_aFilename));
296 EXPECT_FALSE(fs_rename(Info.m_aFilename, aNewFilename)); // Can rename a file that has multiple open handles.
297 EXPECT_FALSE(fs_is_file(Info.m_aFilename)); // Rename should take effect immediately.
298 EXPECT_TRUE(fs_is_file(aNewFilename));
299
300 EXPECT_FALSE(io_close(FileWrite1));
301 EXPECT_FALSE(io_close(FileRead1));
302 EXPECT_FALSE(io_close(FileWrite2));
303 EXPECT_FALSE(io_close(FileRead2));
304
305 EXPECT_FALSE(fs_remove(aNewFilename));
306}
307
308TEST(Filesystem, RenameTargetFileExists)
309{
310 char aNewFilename[IO_MAX_PATH_LENGTH];
311 CTestInfo Info;
312 Info.Filename(pBuffer: aNewFilename, BufferLength: sizeof(aNewFilename), pSuffix: ".renamed");
313
314 IOHANDLE FileWrite1 = io_open(filename: Info.m_aFilename, flags: IOFLAG_WRITE);
315 ASSERT_TRUE(FileWrite1);
316 EXPECT_FALSE(io_close(FileWrite1));
317
318 IOHANDLE FileWrite2 = io_open(filename: aNewFilename, flags: IOFLAG_WRITE);
319 ASSERT_TRUE(FileWrite2);
320 EXPECT_FALSE(io_close(FileWrite2));
321
322 EXPECT_TRUE(fs_is_file(Info.m_aFilename));
323 EXPECT_TRUE(fs_is_file(aNewFilename));
324 EXPECT_FALSE(fs_rename(Info.m_aFilename, aNewFilename)); // Renaming can overwrite the existing target file if it has no open handles.
325 EXPECT_FALSE(fs_is_file(Info.m_aFilename));
326 EXPECT_TRUE(fs_is_file(aNewFilename));
327
328 EXPECT_FALSE(fs_remove(aNewFilename));
329}
330
331TEST(Filesystem, RenameOpenFileDeleteTarget)
332{
333 char aNewFilename[IO_MAX_PATH_LENGTH];
334 CTestInfo Info;
335 Info.Filename(pBuffer: aNewFilename, BufferLength: sizeof(aNewFilename), pSuffix: ".renamed");
336
337 IOHANDLE FileWrite1 = io_open(filename: Info.m_aFilename, flags: IOFLAG_WRITE);
338 ASSERT_TRUE(FileWrite1);
339 EXPECT_FALSE(io_close(FileWrite1));
340
341 IOHANDLE FileWrite2 = io_open(filename: aNewFilename, flags: IOFLAG_WRITE);
342 ASSERT_TRUE(FileWrite2);
343 EXPECT_FALSE(io_close(FileWrite2));
344
345 IOHANDLE FileRead = io_open(filename: aNewFilename, flags: IOFLAG_READ);
346 ASSERT_TRUE(FileRead);
347
348 EXPECT_TRUE(fs_is_file(Info.m_aFilename));
349 EXPECT_TRUE(fs_is_file(aNewFilename));
350 EXPECT_FALSE(fs_remove(aNewFilename)); // Target file must be deleted else rename fails on Windows when target file has open handle.
351 EXPECT_FALSE(fs_rename(Info.m_aFilename, aNewFilename));
352 EXPECT_FALSE(fs_is_file(Info.m_aFilename));
353 EXPECT_TRUE(fs_is_file(aNewFilename));
354
355 EXPECT_FALSE(io_close(FileRead));
356
357 EXPECT_FALSE(fs_remove(aNewFilename));
358}
359