diff options
| -rw-r--r-- | build/visual-studio/slang-unit-test-tool/slang-unit-test-tool.vcxproj | 1 | ||||
| -rw-r--r-- | build/visual-studio/slang-unit-test-tool/slang-unit-test-tool.vcxproj.filters | 3 | ||||
| -rw-r--r-- | source/core/slang-file-system.h | 2 | ||||
| -rw-r--r-- | source/core/slang-riff-file-system.cpp | 92 | ||||
| -rw-r--r-- | source/core/slang-riff-file-system.h | 39 | ||||
| -rw-r--r-- | tools/slang-unit-test/unit-test-file-system.cpp | 197 |
6 files changed, 281 insertions, 53 deletions
diff --git a/build/visual-studio/slang-unit-test-tool/slang-unit-test-tool.vcxproj b/build/visual-studio/slang-unit-test-tool/slang-unit-test-tool.vcxproj index 1c5473dd8..a6a5dad7d 100644 --- a/build/visual-studio/slang-unit-test-tool/slang-unit-test-tool.vcxproj +++ b/build/visual-studio/slang-unit-test-tool/slang-unit-test-tool.vcxproj @@ -276,6 +276,7 @@ <ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-com-host-callable.cpp" />
<ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-command-line-args.cpp" />
<ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-compression.cpp" />
+ <ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-file-system.cpp" />
<ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-find-type-by-name.cpp" />
<ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-free-list.cpp" />
<ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-io.cpp" />
diff --git a/build/visual-studio/slang-unit-test-tool/slang-unit-test-tool.vcxproj.filters b/build/visual-studio/slang-unit-test-tool/slang-unit-test-tool.vcxproj.filters index 21d943292..c84d21b30 100644 --- a/build/visual-studio/slang-unit-test-tool/slang-unit-test-tool.vcxproj.filters +++ b/build/visual-studio/slang-unit-test-tool/slang-unit-test-tool.vcxproj.filters @@ -29,6 +29,9 @@ <ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-compression.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-file-system.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="..\..\..\tools\slang-unit-test\unit-test-find-type-by-name.cpp">
<Filter>Source Files</Filter>
</ClCompile>
diff --git a/source/core/slang-file-system.h b/source/core/slang-file-system.h index ea432ad30..d83d7d343 100644 --- a/source/core/slang-file-system.h +++ b/source/core/slang-file-system.h @@ -242,6 +242,8 @@ public: virtual SLANG_NO_THROW SlangResult SLANG_MCALL remove(const char* path) SLANG_OVERRIDE; virtual SLANG_NO_THROW SlangResult SLANG_MCALL createDirectory(const char* path) SLANG_OVERRIDE; + /// stripPath will remove any path for an access, making an access always just + /// access the *filename* from the input path, in the contained filesystem at the relative path RelativeFileSystem(ISlangFileSystem* fileSystem, const String& relativePath, bool stripPath = false); protected: diff --git a/source/core/slang-riff-file-system.cpp b/source/core/slang-riff-file-system.cpp index a8e0f2073..9402a2b4e 100644 --- a/source/core/slang-riff-file-system.cpp +++ b/source/core/slang-riff-file-system.cpp @@ -83,8 +83,7 @@ SlangResult RiffFileSystem::_calcCanonicalPath(const char* path, StringBuilder& RiffFileSystem::Entry* RiffFileSystem::_getEntryFromCanonicalPath(const String& canonicalPath) { - RefPtr<Entry>* entryPtr = m_entries.TryGetValue(canonicalPath); - return entryPtr ? *entryPtr : nullptr; + return m_entries.TryGetValue(canonicalPath); } RiffFileSystem::Entry* RiffFileSystem::_getEntryFromPath(const char* path, String* outPath) @@ -170,7 +169,7 @@ SlangResult RiffFileSystem::getPathType(const char* path, SlangPathType* outPath ImplicitDirectoryCollector collector(canonicalPath); for (const auto& pair : m_entries) { - Entry* childEntry = pair.Value; + const Entry* childEntry = &pair.Value; collector.addPath(childEntry->m_type, childEntry->m_canonicalPath.getUnownedSlice()); // If on adding a path we determine a directory exists, then we are done if (collector.getDirectoryExists()) @@ -204,23 +203,20 @@ SlangResult RiffFileSystem::getCanonicalPath(const char* path, ISlangBlob** outC return SLANG_OK; } - SlangResult RiffFileSystem::enumeratePathContents(const char* path, FileSystemContentsCallBack callback, void* userData) { String canonicalPath; Entry* entry = _getEntryFromPath(path, &canonicalPath); - if (entry && entry->m_type != SLANG_PATH_TYPE_DIRECTORY) - { - return SLANG_FAIL; - } - // If we didn't find an explicit directory, lets handle an implicit one - ImplicitDirectoryCollector collector(canonicalPath); + const bool foundDirectory = (entry && entry->m_type == SLANG_PATH_TYPE_DIRECTORY); + + // We allow implicit directories, so this works even if there isn't an explicit one + ImplicitDirectoryCollector collector(canonicalPath, foundDirectory); // If it is a directory, we need to see if there is anything in it for (const auto& pair : m_entries) { - Entry* childEntry = pair.Value; + const Entry* childEntry = &pair.Value; collector.addPath(childEntry->m_type, childEntry->m_canonicalPath.getUnownedSlice()); } @@ -245,20 +241,23 @@ SlangResult RiffFileSystem::saveFile(const char* path, const void* data, size_t contents = RawBlob::create(data, size); } - Entry* entry = _getEntryFromCanonicalPath(canonicalPath); - if (!entry) + if (Entry* foundEntry = _getEntryFromCanonicalPath(canonicalPath)) { - entry = new Entry; - entry->m_type = SLANG_PATH_TYPE_FILE; - entry->m_canonicalPath = canonicalPath; - entry->m_uncompressedSizeInBytes = size; + if (foundEntry->m_type != SLANG_PATH_TYPE_FILE) + { + // Can only set if it's already a file, if it's anything else it's an error + return SLANG_FAIL; + } + foundEntry->setContents(size, contents); + } + else + { + Entry entry; + entry.initFile(canonicalPath, size, contents); m_entries.Add(canonicalPath, entry); } - entry->m_uncompressedSizeInBytes = size; - entry->m_contents = contents; - return SLANG_OK; } @@ -269,26 +268,25 @@ SlangResult RiffFileSystem::remove(const char* path) if (entry) { - if (entry->m_type == SLANG_PATH_TYPE_FILE) + if (entry->m_type == SLANG_PATH_TYPE_DIRECTORY) { - m_entries.Remove(canonicalPath); - return SLANG_OK; - } + ImplicitDirectoryCollector collector(canonicalPath); - ImplicitDirectoryCollector collector(canonicalPath); - - // If it is a directory, we need to see if there is anything in it - for (const auto& pair : m_entries) - { - Entry* childEntry = pair.Value; - collector.addPath(childEntry->m_type, childEntry->m_canonicalPath.getUnownedSlice()); - if (collector.hasContent()) + // If it is a directory, we need to see if there is anything in it + for (const auto& pair : m_entries) { - // Directory is not empty - return SLANG_FAIL; + const Entry* childEntry = &pair.Value; + collector.addPath(childEntry->m_type, childEntry->m_canonicalPath.getUnownedSlice()); + if (collector.hasContent()) + { + // Directory is not empty + return SLANG_FAIL; + } } } + // Reset so doesn't hold references/keep memory in scope + entry->reset(); m_entries.Remove(canonicalPath); return SLANG_OK; } @@ -299,17 +297,13 @@ SlangResult RiffFileSystem::remove(const char* path) SlangResult RiffFileSystem::createDirectory(const char* path) { String canonicalPath; - Entry* entry = _getEntryFromPath(path, &canonicalPath); - if (entry) + if (_getEntryFromPath(path, &canonicalPath)) { return SLANG_FAIL; } - entry = new Entry; - entry->m_type = SLANG_PATH_TYPE_DIRECTORY; - entry->m_canonicalPath = canonicalPath; - entry->m_uncompressedSizeInBytes = 0; - + Entry entry; + entry.initDirectory(canonicalPath); m_entries.Add(canonicalPath, entry); return SLANG_OK; } @@ -384,16 +378,16 @@ SlangResult RiffFileSystem::loadArchive(const void* archive, size_t archiveSizeI return SLANG_FAIL; } - RefPtr<Entry> dstEntry = new Entry; + Entry dstEntry; const char* path = (const char*)srcData; srcData += srcEntry->pathSize; - dstEntry->m_canonicalPath = UnownedStringSlice(path, srcEntry->pathSize - 1); - dstEntry->m_type = (SlangPathType)srcEntry->pathType; - dstEntry->m_uncompressedSizeInBytes = srcEntry->uncompressedSize; + dstEntry.m_canonicalPath = UnownedStringSlice(path, srcEntry->pathSize - 1); + dstEntry.m_type = (SlangPathType)srcEntry->pathType; + dstEntry.m_uncompressedSizeInBytes = srcEntry->uncompressedSize; - switch (dstEntry->m_type) + switch (dstEntry.m_type) { case SLANG_PATH_TYPE_FILE: { @@ -403,7 +397,7 @@ SlangResult RiffFileSystem::loadArchive(const void* archive, size_t archiveSizeI } // Get the compressed data - dstEntry->m_contents = RawBlob::create(srcData, srcEntry->compressedSize); + dstEntry.m_contents = RawBlob::create(srcData, srcEntry->compressedSize); break; } case SLANG_PATH_TYPE_DIRECTORY: break; @@ -411,7 +405,7 @@ SlangResult RiffFileSystem::loadArchive(const void* archive, size_t archiveSizeI } // Add to the list of entries - m_entries.Add(dstEntry->m_canonicalPath, dstEntry); + m_entries.Add(dstEntry.m_canonicalPath, dstEntry); } } @@ -437,7 +431,7 @@ SlangResult RiffFileSystem::storeArchive(bool blobOwnsContent, ISlangBlob** outB { RiffContainer::ScopeChunk scopeData(&container, RiffContainer::Chunk::Kind::Data, RiffFileSystemBinary::kEntryFourCC); - const Entry* srcEntry = pair.Value; + const Entry* srcEntry = &pair.Value; RiffFileSystemBinary::Entry dstEntry; dstEntry.uncompressedSize = 0; diff --git a/source/core/slang-riff-file-system.h b/source/core/slang-riff-file-system.h index 656b14325..6cd66c3d5 100644 --- a/source/core/slang-riff-file-system.h +++ b/source/core/slang-riff-file-system.h @@ -66,15 +66,45 @@ public: virtual SLANG_NO_THROW SlangResult SLANG_MCALL storeArchive(bool blobOwnsContent, ISlangBlob** outBlob) SLANG_OVERRIDE; virtual SLANG_NO_THROW void SLANG_MCALL setCompressionStyle(const CompressionStyle& style) SLANG_OVERRIDE { m_compressionStyle = style; } - RiffFileSystem(ICompressionSystem* compressionSystem); + /// Pass in nullptr, if no compression is wanted. In that scenario the contents will be stored in memory as is + explicit RiffFileSystem(ICompressionSystem* compressionSystem); /// True if this appears to be Riff archive static bool isArchive(const void* data, size_t sizeInBytes); protected: - struct Entry : RefObject + struct Entry { + void reset() + { + m_type = SLANG_PATH_TYPE_FILE; + m_canonicalPath = String(); + m_uncompressedSizeInBytes = 0; + m_contents.setNull(); + } + + void initDirectory(const String& canonicalPath) + { + m_type = SLANG_PATH_TYPE_DIRECTORY; + m_canonicalPath = canonicalPath; + m_uncompressedSizeInBytes = 0; + m_contents.setNull(); + } + void initFile(const String& canonicalPath, size_t uncompressedSize, ISlangBlob* blob) + { + m_type = SLANG_PATH_TYPE_FILE; + m_canonicalPath = canonicalPath; + setContents(uncompressedSize, blob); + } + void setContents(size_t uncompressedSize, ISlangBlob* blob) + { + SLANG_ASSERT(m_type == SLANG_PATH_TYPE_FILE); + SLANG_ASSERT(blob); + m_uncompressedSizeInBytes = uncompressedSize; + m_contents = blob; + } + SlangPathType m_type; String m_canonicalPath; size_t m_uncompressedSizeInBytes; ///< Needed if m_contents is compressed. @@ -88,10 +118,11 @@ protected: Entry* _getEntryFromPath(const char* path, String* outPath = nullptr); Entry* _getEntryFromCanonicalPath(const String& canonicalPath); - void _clear() { m_entries.Clear(); } + /// Clear, ensures any backing memory is also freed + void _clear() { m_entries = Dictionary<String, Entry>(); } // Maps a path to an entry - Dictionary<String, RefPtr<Entry>> m_entries; + Dictionary<String, Entry> m_entries; ComPtr<ICompressionSystem> m_compressionSystem; diff --git a/tools/slang-unit-test/unit-test-file-system.cpp b/tools/slang-unit-test/unit-test-file-system.cpp new file mode 100644 index 000000000..f1fd2c6bc --- /dev/null +++ b/tools/slang-unit-test/unit-test-file-system.cpp @@ -0,0 +1,197 @@ +// unit-test-file-system.cpp + +#include "../../source/core/slang-file-system.h" +#include "../../source/core/slang-riff-file-system.h" +#include "../../source/core/slang-zip-file-system.h" + +#include "../../source/core/slang-deflate-compression-system.h" +#include "../../source/core/slang-lz4-compression-system.h" + +#include "tools/unit-test/slang-unit-test.h" + +using namespace Slang; + +namespace { + +enum class FileSystemType +{ + Zip, + RiffUncompressed, + RiffDeflate, + RiffLZ4, + Relative, + CountOf, +}; + +struct Entry +{ + typedef Entry ThisType; + + bool operator<(const ThisType& rhs) const { return name < rhs.name; } + bool operator==(const ThisType& rhs) const { return name == rhs.name && type == rhs.type; } + bool operator!=(const ThisType& rhs) const { return !(*this == rhs); } + + SlangPathType type; + String name; +}; + +} // + +static SlangResult _createAndCheckFile(ISlangMutableFileSystem* fileSystem, const char* path, const char* contents) +{ + UnownedStringSlice contentsSlice(contents); + + SLANG_RETURN_ON_FAIL(fileSystem->saveFile(path, contentsSlice.begin(), contentsSlice.getLength())); + + SlangPathType pathType; + SLANG_RETURN_ON_FAIL(fileSystem->getPathType(path, &pathType)); + + if (pathType != SLANG_PATH_TYPE_FILE) + { + return SLANG_FAIL; + } + + ComPtr<ISlangBlob> blob; + SLANG_RETURN_ON_FAIL(fileSystem->loadFile(path, blob.writeRef())); + + if (blob->getBufferSize() != contentsSlice.getLength()) + { + return SLANG_FAIL; + } + if (contentsSlice != UnownedStringSlice((const char*)blob->getBufferPointer(), blob->getBufferSize())) + { + return SLANG_FAIL; + } + + return SLANG_OK; +} + +static SlangResult _createAndCheckDirectory(ISlangMutableFileSystem* fileSystem, const char* path) +{ + SLANG_RETURN_ON_FAIL(fileSystem->createDirectory(path)); + + SlangPathType pathType; + SLANG_RETURN_ON_FAIL(fileSystem->getPathType(path, &pathType)); + + if (pathType != SLANG_PATH_TYPE_DIRECTORY) + { + return SLANG_FAIL; + } + + return SLANG_OK; +} + +static void _entryCallback(SlangPathType pathType, const char* name, void* userData) +{ + List<Entry>& out = *(List<Entry>*)userData; + out.add(Entry{pathType, name}); +} + +static SlangResult _enumeratePath(ISlangMutableFileSystem* fileSystem, const char* path, const ConstArrayView<Entry>& entries) +{ + List<Entry> contents; + + SLANG_RETURN_ON_FAIL(fileSystem->enumeratePathContents(path, _entryCallback, (void*)&contents)); + + contents.sort(); + + if (contents.getArrayView() != entries) + { + return SLANG_FAIL; + } + + return SLANG_OK; +} + +static SlangResult _test(FileSystemType type) +{ + ComPtr<ISlangMutableFileSystem> fileSystem; + + switch (type) + { + case FileSystemType::Zip: + { + SLANG_RETURN_ON_FAIL(ZipFileSystem::create(fileSystem)); + break; + } + case FileSystemType::RiffUncompressed: + { + fileSystem = new RiffFileSystem(nullptr); + break; + } + case FileSystemType::RiffDeflate: + { + fileSystem = new RiffFileSystem(DeflateCompressionSystem::getSingleton()); + break; + } + case FileSystemType::RiffLZ4: + { + fileSystem = new RiffFileSystem(LZ4CompressionSystem::getSingleton()); + break; + } + case FileSystemType::Relative: + { + ComPtr<ISlangMutableFileSystem> memoryFileSystem(new RiffFileSystem(nullptr)); + memoryFileSystem->createDirectory("base"); + + fileSystem = new RelativeFileSystem(memoryFileSystem, "base"); + break; + } + } + + + SLANG_RETURN_ON_FAIL(_createAndCheckFile(fileSystem, "a", "someText")); + SLANG_RETURN_ON_FAIL(_createAndCheckFile(fileSystem, "b", "A longer bit of text....")); + + SLANG_RETURN_ON_FAIL(_createAndCheckDirectory(fileSystem, "d")); + SLANG_RETURN_ON_FAIL(_createAndCheckFile(fileSystem, "d/a", "Some more silly stuff")); + SLANG_RETURN_ON_FAIL(_createAndCheckFile(fileSystem, "d\\b", "Lets go empty")); + + // Lets find all the files in the directory + + { + const Entry entries[] = { {SLANG_PATH_TYPE_FILE, "a" }, {SLANG_PATH_TYPE_FILE, "b" } }; + SLANG_RETURN_ON_FAIL(_enumeratePath(fileSystem, "d", makeConstArrayView(entries))); + } + + { + const Entry entries[] = { {SLANG_PATH_TYPE_FILE, "a" }, {SLANG_PATH_TYPE_FILE, "b" }, {SLANG_PATH_TYPE_DIRECTORY, "d" } }; + SLANG_RETURN_ON_FAIL(_enumeratePath(fileSystem, ".", makeConstArrayView(entries))); + } + + SLANG_RETURN_ON_FAIL(fileSystem->remove("d/a")); + { + const Entry entries[] = { {SLANG_PATH_TYPE_FILE, "b" } }; + SLANG_RETURN_ON_FAIL(_enumeratePath(fileSystem, "d", makeConstArrayView(entries))); + } + SLANG_RETURN_ON_FAIL(fileSystem->remove("d\\b")); + { + SLANG_RETURN_ON_FAIL(_enumeratePath(fileSystem, "d", makeConstArrayView((const Entry*)nullptr, 0))); + } + + // If it's removed it can't be removed again + SLANG_CHECK(SLANG_FAILED(fileSystem->remove("d\\b"))); + + // Remove the directory + SLANG_RETURN_ON_FAIL(fileSystem->remove("d")); + + { + const Entry entries[] = { {SLANG_PATH_TYPE_FILE, "a" }, {SLANG_PATH_TYPE_FILE, "b" } }; + SLANG_RETURN_ON_FAIL(_enumeratePath(fileSystem, ".", makeConstArrayView(entries))); + } + + return SLANG_OK; +} + +SLANG_UNIT_TEST(fileSystem) +{ + for (Index i = 0; i < Count(FileSystemType::CountOf); ++i) + { + const auto type = FileSystemType(i); + + auto const res = _test(type); + + SLANG_CHECK(SLANG_SUCCEEDED(res)); + } +} + |
