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 EXPECT_FALSE(fs_parent_dir(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, RenameFileCaseOnly)
243{
244 char aOldFilename[IO_MAX_PATH_LENGTH];
245 char aNewFilename[IO_MAX_PATH_LENGTH];
246 CTestInfo Info;
247 Info.Filename(pBuffer: aOldFilename, BufferLength: sizeof(aOldFilename), pSuffix: ".case.tmp");
248 Info.Filename(pBuffer: aNewFilename, BufferLength: sizeof(aNewFilename), pSuffix: ".CASE.tmp");
249
250 IOHANDLE FileWrite = io_open(filename: aOldFilename, flags: IOFLAG_WRITE);
251 ASSERT_TRUE(FileWrite);
252 EXPECT_FALSE(io_close(FileWrite));
253
254 EXPECT_TRUE(fs_is_file(aOldFilename));
255 EXPECT_FALSE(fs_rename(aOldFilename, aNewFilename));
256 EXPECT_TRUE(fs_is_file(aNewFilename));
257
258 EXPECT_FALSE(fs_remove(aNewFilename));
259}
260
261TEST(Filesystem, RenameOpenFileCaseOnly)
262{
263 char aOldFilename[IO_MAX_PATH_LENGTH];
264 char aNewFilename[IO_MAX_PATH_LENGTH];
265 CTestInfo Info;
266 Info.Filename(pBuffer: aOldFilename, BufferLength: sizeof(aOldFilename), pSuffix: ".case.tmp");
267 Info.Filename(pBuffer: aNewFilename, BufferLength: sizeof(aNewFilename), pSuffix: ".CASE.tmp");
268
269 IOHANDLE FileWrite = io_open(filename: aOldFilename, flags: IOFLAG_WRITE);
270 ASSERT_TRUE(FileWrite);
271
272 EXPECT_TRUE(fs_is_file(aOldFilename));
273 EXPECT_FALSE(fs_rename(aOldFilename, aNewFilename));
274 EXPECT_TRUE(fs_is_file(aNewFilename));
275
276 EXPECT_FALSE(io_close(FileWrite));
277
278 EXPECT_FALSE(fs_remove(aNewFilename));
279}
280
281TEST(Filesystem, RenameFolder)
282{
283 char aNewFilename[IO_MAX_PATH_LENGTH];
284 CTestInfo Info;
285 Info.Filename(pBuffer: aNewFilename, BufferLength: sizeof(aNewFilename), pSuffix: ".renamed");
286
287 EXPECT_FALSE(fs_makedir(Info.m_aFilename));
288
289 EXPECT_TRUE(fs_is_dir(Info.m_aFilename));
290 EXPECT_FALSE(fs_rename(Info.m_aFilename, aNewFilename));
291 EXPECT_FALSE(fs_is_dir(Info.m_aFilename));
292 EXPECT_TRUE(fs_is_dir(aNewFilename));
293
294 EXPECT_FALSE(fs_removedir(aNewFilename));
295}
296
297TEST(Filesystem, RenameOpenFileSource)
298{
299 char aNewFilename[IO_MAX_PATH_LENGTH];
300 CTestInfo Info;
301 Info.Filename(pBuffer: aNewFilename, BufferLength: sizeof(aNewFilename), pSuffix: ".renamed");
302
303 IOHANDLE FileWrite = io_open(filename: Info.m_aFilename, flags: IOFLAG_WRITE);
304 ASSERT_TRUE(FileWrite);
305
306 EXPECT_TRUE(fs_is_file(Info.m_aFilename));
307 EXPECT_FALSE(fs_rename(Info.m_aFilename, aNewFilename)); // Can rename a file that has open handle.
308 EXPECT_FALSE(fs_is_file(Info.m_aFilename)); // Rename should take effect immediately.
309 EXPECT_TRUE(fs_is_file(aNewFilename));
310
311 EXPECT_FALSE(io_close(FileWrite));
312
313 EXPECT_FALSE(fs_remove(aNewFilename));
314}
315
316TEST(Filesystem, RenameOpenFileSourceMultipleHandles)
317{
318 char aNewFilename[IO_MAX_PATH_LENGTH];
319 CTestInfo Info;
320 Info.Filename(pBuffer: aNewFilename, BufferLength: sizeof(aNewFilename), pSuffix: ".renamed");
321
322 IOHANDLE FileWrite1 = io_open(filename: Info.m_aFilename, flags: IOFLAG_WRITE);
323 ASSERT_TRUE(FileWrite1);
324
325 IOHANDLE FileRead1 = io_open(filename: Info.m_aFilename, flags: IOFLAG_READ);
326 ASSERT_TRUE(FileRead1);
327
328 IOHANDLE FileWrite2 = io_open(filename: Info.m_aFilename, flags: IOFLAG_WRITE);
329 ASSERT_TRUE(FileWrite2);
330
331 IOHANDLE FileRead2 = io_open(filename: Info.m_aFilename, flags: IOFLAG_READ);
332 ASSERT_TRUE(FileRead2);
333
334 EXPECT_TRUE(fs_is_file(Info.m_aFilename));
335 EXPECT_FALSE(fs_rename(Info.m_aFilename, aNewFilename)); // Can rename a file that has multiple open handles.
336 EXPECT_FALSE(fs_is_file(Info.m_aFilename)); // Rename should take effect immediately.
337 EXPECT_TRUE(fs_is_file(aNewFilename));
338
339 EXPECT_FALSE(io_close(FileWrite1));
340 EXPECT_FALSE(io_close(FileRead1));
341 EXPECT_FALSE(io_close(FileWrite2));
342 EXPECT_FALSE(io_close(FileRead2));
343
344 EXPECT_FALSE(fs_remove(aNewFilename));
345}
346
347TEST(Filesystem, RenameTargetFileExists)
348{
349 char aNewFilename[IO_MAX_PATH_LENGTH];
350 CTestInfo Info;
351 Info.Filename(pBuffer: aNewFilename, BufferLength: sizeof(aNewFilename), pSuffix: ".renamed");
352
353 IOHANDLE FileWrite1 = io_open(filename: Info.m_aFilename, flags: IOFLAG_WRITE);
354 ASSERT_TRUE(FileWrite1);
355 EXPECT_FALSE(io_close(FileWrite1));
356
357 IOHANDLE FileWrite2 = io_open(filename: aNewFilename, flags: IOFLAG_WRITE);
358 ASSERT_TRUE(FileWrite2);
359 EXPECT_FALSE(io_close(FileWrite2));
360
361 EXPECT_TRUE(fs_is_file(Info.m_aFilename));
362 EXPECT_TRUE(fs_is_file(aNewFilename));
363 EXPECT_FALSE(fs_rename(Info.m_aFilename, aNewFilename)); // Renaming can overwrite the existing target file if it has no open handles.
364 EXPECT_FALSE(fs_is_file(Info.m_aFilename));
365 EXPECT_TRUE(fs_is_file(aNewFilename));
366
367 EXPECT_FALSE(fs_remove(aNewFilename));
368}
369
370TEST(Filesystem, RenameOpenFileDeleteTarget)
371{
372 char aNewFilename[IO_MAX_PATH_LENGTH];
373 CTestInfo Info;
374 Info.Filename(pBuffer: aNewFilename, BufferLength: sizeof(aNewFilename), pSuffix: ".renamed");
375
376 IOHANDLE FileWrite1 = io_open(filename: Info.m_aFilename, flags: IOFLAG_WRITE);
377 ASSERT_TRUE(FileWrite1);
378 EXPECT_FALSE(io_close(FileWrite1));
379
380 IOHANDLE FileWrite2 = io_open(filename: aNewFilename, flags: IOFLAG_WRITE);
381 ASSERT_TRUE(FileWrite2);
382 EXPECT_FALSE(io_close(FileWrite2));
383
384 IOHANDLE FileRead = io_open(filename: aNewFilename, flags: IOFLAG_READ);
385 ASSERT_TRUE(FileRead);
386
387 EXPECT_TRUE(fs_is_file(Info.m_aFilename));
388 EXPECT_TRUE(fs_is_file(aNewFilename));
389 EXPECT_FALSE(fs_rename(Info.m_aFilename, aNewFilename)); // Renaming can overwrite the existing target file even if it has open handles.
390 EXPECT_FALSE(fs_is_file(Info.m_aFilename));
391 EXPECT_TRUE(fs_is_file(aNewFilename));
392
393 EXPECT_FALSE(io_close(FileRead));
394
395 EXPECT_FALSE(fs_remove(aNewFilename));
396}
397