yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

Ellie HermaszewskaCorrect include dir for libslang (#5539)7b570feed

master
16.6 KiB577 linesraw
1// unit-test-file-system.cpp
2
3#include "../../source/core/slang-castable.h"
4#include "../../source/core/slang-deflate-compression-system.h"
5#include "../../source/core/slang-file-system.h"
6#include "../../source/core/slang-io.h"
7#include "../../source/core/slang-lz4-compression-system.h"
8#include "../../source/core/slang-memory-file-system.h"
9#include "../../source/core/slang-riff-file-system.h"
10#include "../../source/core/slang-zip-file-system.h"
11#include "unit-test/slang-unit-test.h"
12
13using namespace Slang;
14
15namespace
16{ // anonymous
17
18enum class FileSystemType
19{
20    Zip,
21    RiffUncompressed,
22    RiffDeflate,
23    RiffLZ4,
24    Memory,
25    Relative,
26    CountOf,
27};
28
29struct Entry
30{
31    typedef Entry ThisType;
32
33    bool operator<(const ThisType& rhs) const { return path < rhs.path; }
34    bool operator==(const ThisType& rhs) const { return path == rhs.path && type == rhs.type; }
35    bool operator!=(const ThisType& rhs) const { return !(*this == rhs); }
36
37    SlangPathType type;
38    String path;
39};
40
41} // namespace
42
43static SlangResult _checkFile(
44    ISlangFileSystemExt* fileSystem,
45    const char* path,
46    const UnownedStringSlice& contentsSlice)
47{
48    SlangPathType pathType;
49    SLANG_RETURN_ON_FAIL(fileSystem->getPathType(path, &pathType));
50
51    if (pathType != SLANG_PATH_TYPE_FILE)
52    {
53        return SLANG_FAIL;
54    }
55
56    ComPtr<ISlangBlob> blob;
57    SLANG_RETURN_ON_FAIL(fileSystem->loadFile(path, blob.writeRef()));
58
59    if (blob->getBufferSize() != contentsSlice.getLength())
60    {
61        return SLANG_FAIL;
62    }
63    if (contentsSlice !=
64        UnownedStringSlice((const char*)blob->getBufferPointer(), blob->getBufferSize()))
65    {
66        return SLANG_FAIL;
67    }
68    return SLANG_OK;
69}
70
71static SlangResult _checkFile(
72    ISlangMutableFileSystem* fileSystem,
73    const char* path,
74    const char* contents)
75{
76    return _checkFile(fileSystem, path, UnownedStringSlice(contents));
77}
78
79static SlangResult _checkDirectoryExists(ISlangFileSystemExt* fileSystem, const char* path)
80{
81    SlangPathType pathType;
82    SLANG_RETURN_ON_FAIL(fileSystem->getPathType(path, &pathType));
83
84    if (pathType != SLANG_PATH_TYPE_DIRECTORY)
85    {
86        return SLANG_FAIL;
87    }
88    return SLANG_OK;
89}
90
91static SlangResult _createAndCheckFile(
92    ISlangMutableFileSystem* fileSystem,
93    const char* path,
94    const char* contents)
95{
96    UnownedStringSlice contentsSlice(contents);
97
98    SLANG_RETURN_ON_FAIL(
99        fileSystem->saveFile(path, contentsSlice.begin(), contentsSlice.getLength()));
100    SLANG_RETURN_ON_FAIL(_checkFile(fileSystem, path, contentsSlice));
101
102    // Delete it
103    SLANG_RETURN_ON_FAIL(fileSystem->remove(path));
104
105    // Check it's gone
106    SlangPathType pathType;
107    if (SLANG_SUCCEEDED(fileSystem->getPathType(path, &pathType)))
108    {
109        return SLANG_FAIL;
110    }
111
112    // Save as a blob
113    ComPtr<ISlangBlob> blob = RawBlob::create(contentsSlice.begin(), contentsSlice.getLength());
114
115    SLANG_RETURN_ON_FAIL(fileSystem->saveFileBlob(path, blob));
116    SLANG_RETURN_ON_FAIL(_checkFile(fileSystem, path, contentsSlice));
117
118    return SLANG_OK;
119}
120
121static bool _areEqual(ISlangBlob* a, ISlangBlob* b)
122{
123    if (a == b)
124    {
125        return true;
126    }
127    if ((!a || !b) || (a->getBufferSize() != b->getBufferSize()))
128    {
129        return false;
130    }
131
132    return ::memcmp(a->getBufferPointer(), b->getBufferPointer(), a->getBufferSize()) == 0;
133}
134
135static SlangResult _checkCanonical(
136    ISlangMutableFileSystem* fileSystem,
137    const char* const* paths,
138    Count count)
139{
140    if (count <= 0)
141    {
142        return SLANG_FAIL;
143    }
144
145    // The path has to exist to something for canonicalization to be relied upon
146    SlangPathType pathType;
147    SLANG_RETURN_ON_FAIL(fileSystem->getPathType(paths[0], &pathType));
148
149    String canonicalPath;
150    {
151        ComPtr<ISlangBlob> blob;
152        SLANG_RETURN_ON_FAIL(fileSystem->getPath(PathKind::Canonical, paths[0], blob.writeRef()));
153        canonicalPath = StringUtil::getString(blob);
154    }
155
156    // The canonicalized path must point to the same thing
157    SlangPathType canonicalPathType;
158    SLANG_RETURN_ON_FAIL(fileSystem->getPathType(canonicalPath.getBuffer(), &canonicalPathType));
159
160    if (canonicalPathType != pathType)
161    {
162        return SLANG_FAIL;
163    }
164
165    // If they are the file, being hte same file, they must hold the same data...
166    if (pathType == SLANG_PATH_TYPE_FILE)
167    {
168        ComPtr<ISlangBlob> blob;
169        ComPtr<ISlangBlob> canonicalPathBlob;
170        SLANG_RETURN_ON_FAIL(fileSystem->loadFile(paths[0], blob.writeRef()));
171        SLANG_RETURN_ON_FAIL(
172            fileSystem->loadFile(canonicalPath.getBuffer(), canonicalPathBlob.writeRef()));
173
174        if (!_areEqual(blob, canonicalPathBlob))
175        {
176            return SLANG_FAIL;
177        }
178    }
179
180    for (Index i = 1; i < count; ++i)
181    {
182        ComPtr<ISlangBlob> blob;
183        SLANG_RETURN_ON_FAIL(fileSystem->getPath(PathKind::Canonical, paths[i], blob.writeRef()));
184        const auto checkPath = StringUtil::getString(blob);
185
186        if (checkPath != canonicalPath)
187        {
188            return SLANG_FAIL;
189        }
190    }
191
192    return SLANG_OK;
193}
194
195
196static SlangResult _createAndCheckDirectory(ISlangMutableFileSystem* fileSystem, const char* path)
197{
198    SLANG_RETURN_ON_FAIL(fileSystem->createDirectory(path));
199
200    SlangPathType pathType;
201    SLANG_RETURN_ON_FAIL(fileSystem->getPathType(path, &pathType));
202
203    if (pathType != SLANG_PATH_TYPE_DIRECTORY)
204    {
205        return SLANG_FAIL;
206    }
207
208    return SLANG_OK;
209}
210
211static void _entryCallback(SlangPathType pathType, const char* name, void* userData)
212{
213    List<Entry>& out = *(List<Entry>*)userData;
214    out.add(Entry{pathType, name});
215}
216
217static SlangResult _enumeratePath(
218    ISlangFileSystemExt* fileSystem,
219    const char* path,
220    const ConstArrayView<Entry>& entries)
221{
222    List<Entry> contents;
223
224    SLANG_RETURN_ON_FAIL(fileSystem->enumeratePathContents(path, _entryCallback, (void*)&contents));
225
226    contents.sort();
227
228    if (contents.getArrayView() != entries)
229    {
230        return SLANG_FAIL;
231    }
232
233    return SLANG_OK;
234}
235
236static SlangResult _checkSimplifiedPath(
237    ISlangFileSystemExt* fileSystem,
238    const char* path,
239    const char* normalPath)
240{
241    ComPtr<ISlangBlob> simplifiedPathBlob;
242    SLANG_RETURN_ON_FAIL(
243        fileSystem->getPath(PathKind::Simplified, path, simplifiedPathBlob.writeRef()));
244
245    auto simplifiedPath = StringUtil::getString(simplifiedPathBlob);
246
247    if (simplifiedPath != normalPath)
248    {
249        return SLANG_FAIL;
250    }
251
252    return SLANG_OK;
253}
254
255SlangResult _appendPathEntries(
256    ISlangFileSystemExt* fileSystem,
257    const char* inBasePath,
258    List<Entry>& outEntries)
259{
260    const UnownedStringSlice basePath(inBasePath);
261    if (basePath == toSlice(".") || basePath.getLength() == 0)
262    {
263        // We don't need to append path prefixes if we are at the root.
264        SLANG_RETURN_ON_FAIL(
265            fileSystem->enumeratePathContents(inBasePath, _entryCallback, (void*)&outEntries));
266    }
267    else
268    {
269        const Index startIndex = outEntries.getCount();
270        SLANG_RETURN_ON_FAIL(
271            fileSystem->enumeratePathContents(inBasePath, _entryCallback, (void*)&outEntries));
272
273        const String basePathString(basePath);
274
275        // we need to fix all of the added paths to make absolute
276        const Count count = outEntries.getCount();
277        for (Index i = startIndex; i < count; ++i)
278        {
279            auto& entry = outEntries[i];
280            entry.path = Path::combine(basePathString, entry.path);
281        }
282    }
283
284    return SLANG_OK;
285}
286
287static SlangResult _getAllEntries(
288    ISlangFileSystemExt* fileSystem,
289    const char* inBasePath,
290    List<Entry>& outEntries)
291{
292    outEntries.clear();
293
294    // Simplify the base
295    auto basePath = Path::simplify(inBasePath);
296
297    _appendPathEntries(fileSystem, basePath.getBuffer(), outEntries);
298
299    for (Index i = 0; i < outEntries.getCount(); ++i)
300    {
301        // We need to make a copy as outEntries is mutated
302        const Entry entry = outEntries[i];
303        if (entry.type == SLANG_PATH_TYPE_DIRECTORY)
304        {
305            _appendPathEntries(fileSystem, entry.path.getBuffer(), outEntries);
306        }
307    }
308
309    // Sort to remove issues with traversal ordering
310    outEntries.sort();
311    return SLANG_OK;
312}
313
314static SlangResult _checkEqual(ISlangFileSystemExt* a, ISlangFileSystemExt* b)
315{
316    List<Entry> aEntries, bEntries;
317
318    SLANG_RETURN_ON_FAIL(_getAllEntries(a, ".", aEntries));
319    SLANG_RETURN_ON_FAIL(_getAllEntries(b, ".", bEntries));
320
321    if (aEntries != bEntries)
322    {
323        return SLANG_FAIL;
324    }
325
326    // For all the files check the contents is the same
327
328    for (const auto& entry : aEntries)
329    {
330        if (entry.type != SLANG_PATH_TYPE_FILE)
331        {
332            continue;
333        }
334
335        ComPtr<ISlangBlob> blobA, blobB;
336
337        SLANG_RETURN_ON_FAIL(a->loadFile(entry.path.getBuffer(), blobA.writeRef()));
338        SLANG_RETURN_ON_FAIL(b->loadFile(entry.path.getBuffer(), blobB.writeRef()));
339
340        if (blobA->getBufferSize() != blobB->getBufferSize())
341        {
342            return SLANG_FAIL;
343        }
344
345        if (::memcmp(
346                blobA->getBufferPointer(),
347                blobB->getBufferPointer(),
348                blobA->getBufferSize()) != 0)
349        {
350            return SLANG_FAIL;
351        }
352    }
353
354    return SLANG_OK;
355}
356
357static SlangResult _createFileSystem(
358    FileSystemType type,
359    ComPtr<ISlangMutableFileSystem>& outFileSystem)
360{
361    outFileSystem.setNull();
362    switch (type)
363    {
364    case FileSystemType::Zip:
365        return ZipFileSystem::create(outFileSystem);
366    case FileSystemType::RiffUncompressed:
367        outFileSystem = new RiffFileSystem(nullptr);
368        break;
369    case FileSystemType::RiffDeflate:
370        outFileSystem = new RiffFileSystem(DeflateCompressionSystem::getSingleton());
371        break;
372    case FileSystemType::RiffLZ4:
373        outFileSystem = new RiffFileSystem(LZ4CompressionSystem::getSingleton());
374        break;
375    case FileSystemType::Memory:
376        outFileSystem = new MemoryFileSystem;
377        break;
378    case FileSystemType::Relative:
379        {
380            ComPtr<ISlangMutableFileSystem> memoryFileSystem(new MemoryFileSystem);
381            memoryFileSystem->createDirectory("base");
382
383            outFileSystem = new RelativeFileSystem(memoryFileSystem, "base");
384            break;
385        }
386    }
387
388    return outFileSystem ? SLANG_OK : SLANG_FAIL;
389}
390
391static SlangResult _testImplicitDirectory(FileSystemType type)
392{
393    ComPtr<ISlangMutableFileSystem> fileSystem;
394    SLANG_RETURN_ON_FAIL(_createFileSystem(type, fileSystem));
395
396    const char contents3[] = "Some text....";
397
398    SLANG_RETURN_ON_FAIL(
399        fileSystem->saveFile("implicit-path/file2.txt", contents3, SLANG_COUNT_OF(contents3)));
400
401    {
402        SlangPathType pathType;
403        SLANG_RETURN_ON_FAIL(fileSystem->getPathType("implicit-path", &pathType));
404
405        SLANG_CHECK(pathType == SLANG_PATH_TYPE_DIRECTORY);
406
407        auto checkEntries = [&]() -> SlangResult
408        {
409            List<Entry> entries;
410            SLANG_RETURN_ON_FAIL(_getAllEntries(fileSystem, "implicit-path", entries));
411
412            // It contains a file
413            SLANG_CHECK(entries.getCount() == 1);
414
415            for (const auto& entry : entries)
416            {
417                // All of these should exist
418                SlangPathType pathType;
419                SLANG_RETURN_ON_FAIL(fileSystem->getPathType(entry.path.getBuffer(), &pathType));
420            }
421            return SLANG_OK;
422        };
423
424        SLANG_RETURN_ON_FAIL(checkEntries());
425
426        // Make an explicit path, and see whe have the same results
427        fileSystem->createDirectory("implicit-path");
428
429        SLANG_RETURN_ON_FAIL(checkEntries());
430    }
431
432    return SLANG_OK;
433}
434
435static SlangResult _test(FileSystemType type)
436{
437    ComPtr<ISlangMutableFileSystem> fileSystem;
438    SLANG_RETURN_ON_FAIL(_createFileSystem(type, fileSystem));
439
440    const auto aText = "someText";
441    const auto bText = "A longer bit of text....";
442    const auto d_aText = "Some more silly stuff";
443    const auto d_bText = "Lets go!";
444
445    SLANG_RETURN_ON_FAIL(_createAndCheckFile(fileSystem, "a", aText));
446    SLANG_RETURN_ON_FAIL(_createAndCheckFile(fileSystem, "b", bText));
447
448    SLANG_RETURN_ON_FAIL(_createAndCheckDirectory(fileSystem, "d"));
449    SLANG_RETURN_ON_FAIL(_createAndCheckFile(fileSystem, "d/a", d_aText));
450    SLANG_RETURN_ON_FAIL(_createAndCheckFile(fileSystem, "d\\b", d_bText));
451
452    // Try and absolute path
453    SLANG_RETURN_ON_FAIL(_checkFile(fileSystem, "/a", aText));
454    SLANG_RETURN_ON_FAIL(_checkFile(fileSystem, "/b", bText));
455    SLANG_RETURN_ON_FAIL(_checkFile(fileSystem, "/d/a", d_aText));
456    SLANG_RETURN_ON_FAIL(_checkFile(fileSystem, "/d\\b", d_bText));
457
458
459    // Check canonical on files
460    {
461        const char* paths[] = {"a", "/a", "./a", "d/../a", ".\\d/.\\..\\a"};
462        SLANG_RETURN_ON_FAIL(_checkCanonical(fileSystem, paths, SLANG_COUNT_OF(paths)));
463    }
464
465    {
466        const char* paths[] = {"/d/b", "d/./b"};
467        SLANG_RETURN_ON_FAIL(_checkCanonical(fileSystem, paths, SLANG_COUNT_OF(paths)));
468    }
469
470    // Check canonical on directories
471    {
472        const char* paths[] = {".", "/", "/d/..", "d/.."};
473        SLANG_RETURN_ON_FAIL(_checkCanonical(fileSystem, paths, SLANG_COUNT_OF(paths)));
474    }
475
476    {
477        const char* paths[] = {"d", "./d", "/d", "/d/./../d"};
478        SLANG_RETURN_ON_FAIL(_checkCanonical(fileSystem, paths, SLANG_COUNT_OF(paths)));
479    }
480
481    // Lets find all the files in the directory
482
483    {
484        const Entry entries[] = {{SLANG_PATH_TYPE_FILE, "a"}, {SLANG_PATH_TYPE_FILE, "b"}};
485        SLANG_RETURN_ON_FAIL(_enumeratePath(fileSystem, "d", makeConstArrayView(entries)));
486    }
487
488    {
489        const Entry entries[] = {
490            {SLANG_PATH_TYPE_FILE, "a"},
491            {SLANG_PATH_TYPE_FILE, "b"},
492            {SLANG_PATH_TYPE_DIRECTORY, "d"}};
493        SLANG_RETURN_ON_FAIL(_enumeratePath(fileSystem, ".", makeConstArrayView(entries)));
494
495        // Let's check that / and \ works for the root directory
496        SLANG_RETURN_ON_FAIL(_enumeratePath(fileSystem, "/", makeConstArrayView(entries)));
497        SLANG_RETURN_ON_FAIL(_enumeratePath(fileSystem, "\\", makeConstArrayView(entries)));
498    }
499
500    // Check the root directory exists
501    {
502        SLANG_RETURN_ON_FAIL(_checkDirectoryExists(fileSystem, "."));
503        SLANG_RETURN_ON_FAIL(_checkDirectoryExists(fileSystem, "/"));
504        SLANG_RETURN_ON_FAIL(_checkDirectoryExists(fileSystem, "\\"));
505    }
506
507    {
508        SLANG_RETURN_ON_FAIL(_checkSimplifiedPath(fileSystem, "d/../a", "a"));
509    }
510
511
512    // If we have an archive file system check out it's behavior
513    if (IArchiveFileSystem* archiveFileSystem = as<IArchiveFileSystem>(fileSystem))
514    {
515        // Load and check its okay
516
517        ComPtr<ISlangBlob> archiveBlob;
518        SLANG_RETURN_ON_FAIL(archiveFileSystem->storeArchive(false, archiveBlob.writeRef()));
519
520        ComPtr<ISlangFileSystemExt> loadedFileSystem;
521        SLANG_RETURN_ON_FAIL(loadArchiveFileSystem(
522            archiveBlob->getBufferPointer(),
523            archiveBlob->getBufferSize(),
524            loadedFileSystem));
525
526        // Check the file systems contents are the same
527        SLANG_RETURN_ON_FAIL(_checkEqual(loadedFileSystem, fileSystem));
528    }
529
530    SLANG_RETURN_ON_FAIL(fileSystem->remove("d/a"));
531    {
532        const Entry entries[] = {{SLANG_PATH_TYPE_FILE, "b"}};
533        SLANG_RETURN_ON_FAIL(_enumeratePath(fileSystem, "d", makeConstArrayView(entries)));
534    }
535    SLANG_RETURN_ON_FAIL(fileSystem->remove("d\\b"));
536    {
537        SLANG_RETURN_ON_FAIL(
538            _enumeratePath(fileSystem, "d", makeConstArrayView((const Entry*)nullptr, 0)));
539    }
540
541    // If it's removed it can't be removed again
542    SLANG_CHECK(SLANG_FAILED(fileSystem->remove("d\\b")));
543
544    // Remove the directory
545    SLANG_RETURN_ON_FAIL(fileSystem->remove("d"));
546
547    {
548        const Entry entries[] = {{SLANG_PATH_TYPE_FILE, "a"}, {SLANG_PATH_TYPE_FILE, "b"}};
549        SLANG_RETURN_ON_FAIL(_enumeratePath(fileSystem, ".", makeConstArrayView(entries)));
550    }
551
552    return SLANG_OK;
553}
554
555SLANG_UNIT_TEST(fileSystem)
556{
557    for (Index i = 0; i < Count(FileSystemType::CountOf); ++i)
558    {
559        const auto type = FileSystemType(i);
560
561        SLANG_CHECK(SLANG_SUCCEEDED(_test(type)));
562
563        // Some file system types support 'implicit directories'.
564        // This means that if a file is created with a path, the directories
565        // required to make that path valid are 'implicitly' created.
566        //
567        // Currently this behavior is supported by zip, and this test checks
568        // that it is working correctly, as we require the file system to
569        // behave correctly in other ways irrespectively of if the directory is
570        // implicit or not.
571        const bool hasImplicitDirectory = (type == FileSystemType::Zip);
572        if (hasImplicitDirectory)
573        {
574            SLANG_CHECK(SLANG_SUCCEEDED(_testImplicitDirectory(type)));
575        }
576    }
577}