summaryrefslogtreecommitdiff
path: root/tools/slang-unit-test
diff options
context:
space:
mode:
authorYong He <yonghe@outlook.com>2021-09-24 11:33:44 -0700
committerGitHub <noreply@github.com>2021-09-24 11:33:44 -0700
commitbec8e6aec85b6e3f875c58bdd59eb15613978358 (patch)
tree0791fb2ce1be786c17e5a6ee489ed3065fc07332 /tools/slang-unit-test
parentf2a3c933bc11a498c622fa18694c84beca8ca031 (diff)
Move existing unit tests to a standalone dll. (#1945)
Diffstat (limited to 'tools/slang-unit-test')
-rw-r--r--tools/slang-unit-test/unit-offset-container.cpp117
-rw-r--r--tools/slang-unit-test/unit-test-byte-encode.cpp139
-rw-r--r--tools/slang-unit-test/unit-test-command-line-args.cpp125
-rw-r--r--tools/slang-unit-test/unit-test-compression.cpp189
-rw-r--r--tools/slang-unit-test/unit-test-find-type-by-name.cpp55
-rw-r--r--tools/slang-unit-test/unit-test-free-list.cpp53
-rw-r--r--tools/slang-unit-test/unit-test-json.cpp320
-rw-r--r--tools/slang-unit-test/unit-test-memory-arena.cpp271
-rw-r--r--tools/slang-unit-test/unit-test-path.cpp62
-rw-r--r--tools/slang-unit-test/unit-test-riff.cpp179
-rw-r--r--tools/slang-unit-test/unit-test-short-list.cpp77
-rw-r--r--tools/slang-unit-test/unit-test-string.cpp262
12 files changed, 1849 insertions, 0 deletions
diff --git a/tools/slang-unit-test/unit-offset-container.cpp b/tools/slang-unit-test/unit-offset-container.cpp
new file mode 100644
index 000000000..6a179c319
--- /dev/null
+++ b/tools/slang-unit-test/unit-offset-container.cpp
@@ -0,0 +1,117 @@
+// unit-test-path.cpp
+
+#include "../../source/core/slang-offset-container.h"
+
+#include "tools/unit-test/slang-unit-test.h"
+
+using namespace Slang;
+
+static void _checkEncodeDecode(uint32_t size)
+{
+ uint8_t encode[OffsetString::kMaxSizeEncodeSize];
+
+ size_t encodeSize = OffsetString::calcEncodedSize(size, encode);
+
+ size_t decodedSize;
+ const char* chars = OffsetString::decodeSize((const char*)encode, decodedSize);
+
+ SLANG_CHECK(decodedSize == size);
+ SLANG_CHECK(chars - (const char*)encode == encodeSize);
+}
+
+namespace { // anonymous
+
+struct Root
+{
+ Offset32Array<Offset32Ptr<OffsetString> > dirs;
+ Offset32Ptr<OffsetString> name;
+ float value;
+};
+
+} // anonymous
+
+SLANG_UNIT_TEST(offsetContainer)
+{
+ _checkEncodeDecode(253);
+
+ for (int64_t i = 0; i < 0x100000000; i += (i / 2) + 1)
+ {
+ _checkEncodeDecode(uint32_t(i));
+ }
+
+ {
+ OffsetContainer container;
+
+ const char* strings[] =
+ {
+ "Hello",
+ "World",
+ nullptr,
+ };
+
+ {
+ auto& base = container.asBase();
+
+ Offset32Ptr<Root> root = container.newObject<Root>();
+
+ auto array = container.newArray<Offset32Ptr<OffsetString>>(SLANG_COUNT_OF(strings));
+ for (Int i = 0; i < SLANG_COUNT_OF(strings); ++i)
+ {
+ base[array[i]] = container.newString(strings[i]);
+ }
+ base[root]->dirs = array;
+ }
+
+ {
+ List<uint8_t> copy;
+ copy.addRange(container.getData(), container.getDataCount());
+
+ MemoryOffsetBase base;
+ base.set(copy.getBuffer(), copy.getCount());
+
+ Root* root = (Root*)(copy.getBuffer() + kStartOffset);
+
+ SLANG_CHECK(root->dirs.getCount() == SLANG_COUNT_OF(strings));
+
+ Int count = root->dirs.getCount();
+ for (Int i = 0; i < count; ++i)
+ {
+ OffsetString* str = base.asRaw(base.asRaw(root->dirs[i]));
+
+ const char* check = strings[i];
+
+ if (check)
+ {
+ SLANG_CHECK(str != nullptr);
+ const char* strCstr = str->getCstr();
+ SLANG_CHECK(strcmp(strCstr, check) == 0);
+ }
+ else
+ {
+ SLANG_CHECK(str == nullptr);
+ }
+ }
+
+ {
+ Index index = 0;
+ for (const auto v : root->dirs)
+ {
+ OffsetString* str = base.asRaw(base.asRaw(v));
+ const char* check = strings[index];
+ if (check)
+ {
+ SLANG_CHECK(str != nullptr);
+ const char* strCstr = str->getCstr();
+ SLANG_CHECK(strcmp(strCstr, check) == 0);
+ }
+ else
+ {
+ SLANG_CHECK(str == nullptr);
+ }
+
+ index ++;
+ }
+ }
+ }
+ }
+}
diff --git a/tools/slang-unit-test/unit-test-byte-encode.cpp b/tools/slang-unit-test/unit-test-byte-encode.cpp
new file mode 100644
index 000000000..38bd5561d
--- /dev/null
+++ b/tools/slang-unit-test/unit-test-byte-encode.cpp
@@ -0,0 +1,139 @@
+// unit-test-byte-encode.cpp
+
+#include "../../source/core/slang-byte-encode-util.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "tools/unit-test/slang-unit-test.h"
+
+#include "../../source/core/slang-random-generator.h"
+#include "../../source/core/slang-list.h"
+
+using namespace Slang;
+
+static void checkUInt32(uint32_t value)
+{
+ uint8_t buffer[ByteEncodeUtil::kMaxLiteEncodeUInt32 + 1];
+
+ int writeLen = ByteEncodeUtil::encodeLiteUInt32(value, buffer);
+ buffer[writeLen] = 0xcd;
+
+ uint32_t decode;
+ int readLen = ByteEncodeUtil::decodeLiteUInt32(buffer, &decode);
+
+ SLANG_CHECK(readLen == writeLen && decode == value);
+}
+
+SLANG_UNIT_TEST(byteEncode)
+{
+ DefaultRandomGenerator randGen(0x5346536a);
+
+ {
+ SLANG_CHECK(ByteEncodeUtil::calcMsb8(0) == -1);
+ SLANG_CHECK(ByteEncodeUtil::calcMsb8(1) == 0);
+ SLANG_CHECK(ByteEncodeUtil::calcMsb8(0x81) == 7);
+ }
+
+ {
+ SLANG_CHECK(ByteEncodeUtil::calcMsb32(0) == -1);
+ SLANG_CHECK(ByteEncodeUtil::calcMsb32(0x81) == 7);
+ SLANG_CHECK(ByteEncodeUtil::calcMsb32(0x00000001) == 0);
+ SLANG_CHECK(ByteEncodeUtil::calcMsb32(0x00000081) == 7);
+ SLANG_CHECK(ByteEncodeUtil::calcMsb32(0x00000181) == 8);
+ SLANG_CHECK(ByteEncodeUtil::calcMsb32(0x00008181) == 15);
+ SLANG_CHECK(ByteEncodeUtil::calcMsb32(0x00018181) == 16);
+ SLANG_CHECK(ByteEncodeUtil::calcMsb32(0x00818181) == 23);
+ SLANG_CHECK(ByteEncodeUtil::calcMsb32(0x01818181) == 24);
+ SLANG_CHECK(ByteEncodeUtil::calcMsb32(0x81818181) == 31);
+ SLANG_CHECK(ByteEncodeUtil::calcMsb32(0xffffffff) == 31);
+ }
+
+ {
+ SLANG_CHECK(ByteEncodeUtil::calcMsByte32(0x00000000) == -1);
+ SLANG_CHECK(ByteEncodeUtil::calcMsByte32(0x00000001) == 0);
+ SLANG_CHECK(ByteEncodeUtil::calcMsByte32(0x00000081) == 0);
+ SLANG_CHECK(ByteEncodeUtil::calcMsByte32(0x00000181) == 1);
+ SLANG_CHECK(ByteEncodeUtil::calcMsByte32(0x00008181) == 1);
+ SLANG_CHECK(ByteEncodeUtil::calcMsByte32(0x00018181) == 2);
+ SLANG_CHECK(ByteEncodeUtil::calcMsByte32(0x00818181) == 2);
+ SLANG_CHECK(ByteEncodeUtil::calcMsByte32(0x01818181) == 3);
+ SLANG_CHECK(ByteEncodeUtil::calcMsByte32(0x81818181) == 3);
+ SLANG_CHECK(ByteEncodeUtil::calcMsByte32(0xffffffff) == 3);
+ }
+
+ {
+ SLANG_CHECK(ByteEncodeUtil::calcNonZeroMsByte32(0x00000001) == 0);
+ SLANG_CHECK(ByteEncodeUtil::calcNonZeroMsByte32(0x00000081) == 0);
+ SLANG_CHECK(ByteEncodeUtil::calcNonZeroMsByte32(0x00000181) == 1);
+ SLANG_CHECK(ByteEncodeUtil::calcNonZeroMsByte32(0x00008181) == 1);
+ SLANG_CHECK(ByteEncodeUtil::calcNonZeroMsByte32(0x00018181) == 2);
+ SLANG_CHECK(ByteEncodeUtil::calcNonZeroMsByte32(0x00818181) == 2);
+ SLANG_CHECK(ByteEncodeUtil::calcNonZeroMsByte32(0x01818181) == 3);
+ SLANG_CHECK(ByteEncodeUtil::calcNonZeroMsByte32(0x81818181) == 3);
+ SLANG_CHECK(ByteEncodeUtil::calcNonZeroMsByte32(0xffffffff) == 3);
+ }
+
+ {
+ const int blockSize = 1024;
+
+ List<uint8_t> encodedBuffer;
+ encodedBuffer.setCount(ByteEncodeUtil::kMaxLiteEncodeUInt32 * blockSize);
+
+ List<uint32_t> initialBuffer;
+ initialBuffer.setCount(blockSize);
+ List<uint32_t> decodeBuffer;
+ decodeBuffer.setCount(blockSize);
+ // Put in cache?
+ memset(decodeBuffer.begin(), 0, blockSize * sizeof(uint32_t));
+
+ for (int i = 0; i < blockSize; i++)
+ {
+ const int v = ByteEncodeUtil::calcMsb8(uint32_t((randGen.nextInt32() & 0xf) | 1));
+
+ // Make the commonality of different numbers that bytes are most common, then shorts etc..
+ uint32_t mask;
+ switch (v)
+ {
+ case 0: mask = 0xffffffff; break;
+ case 1: mask = 0x00ffffff; break;
+ case 2: mask = 0x0000ffff; break;
+ case 3: mask = 0x000000ff; break;
+ }
+
+ initialBuffer[i] = randGen.nextInt32() & mask;
+ }
+
+ size_t numEncodeBytes = ByteEncodeUtil::encodeLiteUInt32(initialBuffer.begin(), blockSize, encodedBuffer.begin());
+
+ SLANG_CHECK(ByteEncodeUtil::calcEncodeLiteSizeUInt32(initialBuffer.begin(), blockSize) == numEncodeBytes);
+
+ size_t numEncodeBytes2 = ByteEncodeUtil::decodeLiteUInt32(encodedBuffer.begin(), blockSize, decodeBuffer.begin());
+
+ SLANG_CHECK(numEncodeBytes2 == numEncodeBytes);
+
+ SLANG_CHECK(memcmp(decodeBuffer.begin(), initialBuffer.begin(), sizeof(uint32_t) * blockSize) == 0);
+ }
+
+ {
+ checkUInt32(uint32_t(0));
+ checkUInt32(uint32_t(0x7fffff));
+ checkUInt32(uint32_t(0x7fff));
+ checkUInt32(uint32_t(0x7f));
+ checkUInt32(uint32_t(0x7fffffff));
+ checkUInt32(uint32_t(0xffffffff));
+
+#if 1
+ for (int64_t i = 0; i < SLANG_INT64(0x100000000); i += 371)
+ {
+ checkUInt32(uint32_t(i));
+ }
+#else
+ for (int64_t i = 0; i < SLANG_INT64(0x100000000); i += 1)
+ {
+ checkUInt32(uint32_t(i));
+ }
+#endif
+ }
+
+}
diff --git a/tools/slang-unit-test/unit-test-command-line-args.cpp b/tools/slang-unit-test/unit-test-command-line-args.cpp
new file mode 100644
index 000000000..a4dc8a16c
--- /dev/null
+++ b/tools/slang-unit-test/unit-test-command-line-args.cpp
@@ -0,0 +1,125 @@
+// unit-test-command-line-args.cpp
+
+#include "../../source/compiler-core/slang-command-line-args.h"
+
+#include "tools/unit-test/slang-unit-test.h"
+
+using namespace Slang;
+
+SLANG_UNIT_TEST(commandLineArgs)
+{
+ RefPtr<CommandLineContext> context = new CommandLineContext;
+
+
+ // Simple scoped version
+ {
+ CommandLineArgs args(context);
+ DownstreamArgs downstreamArgs(context);
+
+ DiagnosticSink sink(context->getSourceManager(), nullptr);
+
+ const char* inArgs[] =
+ {
+ "-Xa...",
+ "-blah",
+ "10",
+ "-X.",
+ };
+
+ args.setArgs(inArgs, SLANG_COUNT_OF(inArgs));
+
+ SLANG_CHECK(SLANG_SUCCEEDED(downstreamArgs.stripDownstreamArgs(args, DownstreamArgs::Flag::AllowNewNames, &sink)));
+
+ const char* aArgs[] =
+ {
+ "-blah",
+ "10"
+ };
+
+ SLANG_CHECK(downstreamArgs.getArgsByName("a").hasArgs(aArgs, SLANG_COUNT_OF(aArgs)));
+ SLANG_CHECK(args.getArgCount() == 0 && sink.getErrorCount() == 0);
+ }
+
+ // Leaving off terminating -X. is ok
+ {
+ CommandLineArgs args(context);
+ DownstreamArgs downstreamArgs(context);
+
+ DiagnosticSink sink(context->getSourceManager(), nullptr);
+
+ const char* inArgs[] =
+ {
+ "-Xa...",
+ "-blah",
+ "10",
+ };
+
+ args.setArgs(inArgs, SLANG_COUNT_OF(inArgs));
+
+ SLANG_CHECK(SLANG_SUCCEEDED(downstreamArgs.stripDownstreamArgs(args, DownstreamArgs::Flag::AllowNewNames, &sink)));
+
+ const char* aArgs[] =
+ {
+ "-blah",
+ "10"
+ };
+
+ SLANG_CHECK(downstreamArgs.getArgsByName("a").hasArgs(aArgs, SLANG_COUNT_OF(aArgs)));
+ SLANG_CHECK(args.getArgCount() == 0 && sink.getErrorCount() == 0);
+ }
+
+ // Having a nesting
+
+ {
+ CommandLineArgs args(context);
+ DownstreamArgs downstreamArgs(context);
+
+ DiagnosticSink sink(context->getSourceManager(), nullptr);
+
+ const char* inArgs[] =
+ {
+ "-something",
+ "andAnother",
+ "-Xa...",
+ "-blah",
+ "-Xb...",
+ "-hey",
+ "-X.",
+ "10",
+ "-X.",
+ "-Xc",
+ "somethingForC",
+ };
+
+ args.setArgs(inArgs, SLANG_COUNT_OF(inArgs));
+
+ SLANG_CHECK(SLANG_SUCCEEDED(downstreamArgs.stripDownstreamArgs(args, DownstreamArgs::Flag::AllowNewNames, &sink)));
+
+ const char* aArgs[] =
+ {
+ "-blah",
+ "-Xb...",
+ "-hey",
+ "-X.",
+ "10"
+ };
+
+ const char* cArgs[] =
+ {
+ "somethingForC",
+ };
+
+ const char* mainArgs[] =
+ {
+ "-something",
+ "andAnother",
+ };
+
+ SLANG_CHECK(downstreamArgs.getArgsByName("a").hasArgs(aArgs, SLANG_COUNT_OF(aArgs)));
+ SLANG_CHECK(downstreamArgs.getArgsByName("c").hasArgs(cArgs, SLANG_COUNT_OF(cArgs)));
+
+ SLANG_CHECK(args.hasArgs(mainArgs, SLANG_COUNT_OF(mainArgs)) && sink.getErrorCount() == 0);
+ }
+
+}
+
diff --git a/tools/slang-unit-test/unit-test-compression.cpp b/tools/slang-unit-test/unit-test-compression.cpp
new file mode 100644
index 000000000..89716b443
--- /dev/null
+++ b/tools/slang-unit-test/unit-test-compression.cpp
@@ -0,0 +1,189 @@
+// unit-compression.cpp
+
+#include "tools/unit-test/slang-unit-test.h"
+
+#include "source/core/slang-io.h"
+#include "../../source/core/slang-zip-file-system.h"
+
+#include "../../source/core/slang-lz4-compression-system.h"
+#include "../../source/core/slang-deflate-compression-system.h"
+
+using namespace Slang;
+
+static bool _equals(const void* data, size_t size, ISlangBlob* blob)
+{
+ return blob && blob->getBufferSize() == size && memcmp(blob->getBufferPointer(), data, size) == 0;
+}
+
+template <size_t SIZE>
+static bool _equals(const char (&text)[SIZE], ISlangBlob* blob)
+{
+ return _equals(text, SIZE, blob);
+}
+
+static List<String> _getContents(ISlangFileSystemExt* fileSystem, const char* path)
+{
+ List<String> objs;
+
+ fileSystem->enumeratePathContents(path, [](SlangPathType pathType, const char* name, void* userData) {
+ List<String>& out = *(List<String>*)userData;
+ out.add(name);
+ }, &objs);
+
+ return objs;
+}
+
+SLANG_UNIT_TEST(compression)
+{
+ const SlangArchiveType archiveTypes[] =
+ {
+ SLANG_ARCHIVE_TYPE_RIFF,
+ SLANG_ARCHIVE_TYPE_RIFF_DEFLATE,
+ SLANG_ARCHIVE_TYPE_RIFF_LZ4,
+ SLANG_ARCHIVE_TYPE_ZIP
+ };
+
+ for (auto archiveType : archiveTypes)
+ {
+ // Test out archive file systems
+ RefPtr<ArchiveFileSystem> archiveFileSystem;
+ SLANG_CHECK(SLANG_SUCCEEDED(createArchiveFileSystem(archiveType, archiveFileSystem)));
+
+ const char contents[] = "I'm compressed";
+ const char contents2[] = "Some more stuff";
+ const char contents3[] = "Replace it";
+
+ {
+ ISlangMutableFileSystem* fileSystem = archiveFileSystem;
+
+ SLANG_CHECK(SLANG_SUCCEEDED(fileSystem->createDirectory("hello")));
+ SLANG_CHECK(SLANG_SUCCEEDED(fileSystem->createDirectory("hello2")));
+ SLANG_CHECK(SLANG_SUCCEEDED(fileSystem->remove("hello")));
+ SLANG_CHECK(SLANG_SUCCEEDED(fileSystem->createDirectory("hello")));
+
+ SLANG_CHECK(SLANG_SUCCEEDED(fileSystem->saveFile("file.txt", contents, SLANG_COUNT_OF(contents))));
+
+ SLANG_CHECK(SLANG_SUCCEEDED(fileSystem->saveFile("file2.txt", contents2, SLANG_COUNT_OF(contents2))));
+
+ ComPtr<ISlangBlob> blob;
+ SLANG_CHECK(SLANG_SUCCEEDED(fileSystem->loadFile("file.txt", blob.writeRef())));
+ SLANG_CHECK(_equals(contents, blob));
+
+ SLANG_CHECK(SLANG_SUCCEEDED(fileSystem->loadFile("file2.txt", blob.writeRef())));
+ SLANG_CHECK(_equals(contents2, blob));
+
+ SLANG_CHECK(SLANG_SUCCEEDED(fileSystem->saveFile("file2.txt", contents3, SLANG_COUNT_OF(contents3))));
+
+ SLANG_CHECK(SLANG_SUCCEEDED(fileSystem->loadFile("file2.txt", blob.writeRef())));
+ SLANG_CHECK(_equals(contents3, blob));
+
+ // Check the path type
+ {
+ SlangPathType pathType;
+ SLANG_CHECK(SLANG_SUCCEEDED(fileSystem->getPathType("file2.txt", &pathType)));
+ SLANG_CHECK(pathType == SLANG_PATH_TYPE_FILE);
+
+ SLANG_CHECK(SLANG_SUCCEEDED(fileSystem->getPathType("hello", &pathType)));
+ SLANG_CHECK(pathType == SLANG_PATH_TYPE_DIRECTORY);
+ }
+
+ // Enumerate
+ {
+ for (const auto& obj : _getContents(fileSystem, ""))
+ {
+ // All of these should exist
+ SlangPathType pathType;
+ SLANG_CHECK(SLANG_SUCCEEDED(fileSystem->getPathType(obj.getBuffer(), &pathType)));
+ }
+ }
+
+ SLANG_CHECK(SLANG_SUCCEEDED(fileSystem->saveFile("implicit-path/file2.txt", contents3, SLANG_COUNT_OF(contents3))));
+
+ {
+ SlangPathType pathType;
+ SLANG_CHECK(SLANG_SUCCEEDED(fileSystem->getPathType("implicit-path", &pathType)));
+
+ SLANG_CHECK(pathType == SLANG_PATH_TYPE_DIRECTORY);
+
+ List<String> objs = _getContents(fileSystem, "implicit-path");
+
+ // It contains a file
+ SLANG_CHECK(objs.getCount() == 1);
+
+ for (const auto& obj : objs)
+ {
+ String path = Path::combine("implicit-path", obj);
+
+ // All of these should exist
+ SlangPathType pathType;
+ SLANG_CHECK(SLANG_SUCCEEDED(fileSystem->getPathType(path.getBuffer(), &pathType)));
+ }
+
+ // Make an explicit path, and see whe have the same results
+ fileSystem->createDirectory("implicit-path");
+
+ objs = _getContents(fileSystem, "implicit-path");
+ SLANG_CHECK(objs.getCount() == 1);
+ }
+ }
+
+
+ // Load and check its okay
+
+ {
+ ComPtr<ISlangBlob> archiveBlob;
+ SLANG_CHECK(SLANG_SUCCEEDED(archiveFileSystem->storeArchive(false, archiveBlob.writeRef())));
+
+
+ RefPtr<ArchiveFileSystem> fileSystem;
+#if 0
+ SLANG_CHECK(SLANG_SUCCEEDED(createArchiveFileSystem(archiveType, fileSystem)));
+
+ SLANG_CHECK(SLANG_SUCCEEDED(fileSystem->loadArchive(archiveBlob->getBufferPointer(), archiveBlob->getBufferSize())));
+#else
+ SLANG_CHECK(SLANG_SUCCEEDED(loadArchiveFileSystem(archiveBlob->getBufferPointer(), archiveBlob->getBufferSize(), fileSystem)));
+#endif
+
+ ComPtr<ISlangBlob> blob;
+
+ SLANG_CHECK(SLANG_SUCCEEDED(fileSystem->loadFile("file.txt", blob.writeRef())));
+ SLANG_CHECK(_equals(contents, blob));
+
+ SLANG_CHECK(SLANG_SUCCEEDED(fileSystem->loadFile("file2.txt", blob.writeRef())));
+ SLANG_CHECK(_equals(contents3, blob));
+ }
+ }
+
+ // Test out compression systems
+ for (Index i = 0; i < 2; ++i)
+ {
+ // Lets try lz4
+
+ ICompressionSystem* system = nullptr;
+ if (i == 0)
+ {
+ system = LZ4CompressionSystem::getSingleton();
+ }
+ else
+ {
+ system = DeflateCompressionSystem::getSingleton();
+ }
+
+ const char src[] = "Some text to compress";
+ size_t srcSize = sizeof(src);
+
+ ComPtr<ISlangBlob> compressedBlob;
+
+ CompressionStyle style;
+
+ SLANG_CHECK(SLANG_SUCCEEDED(system->compress(&style, src, srcSize, compressedBlob.writeRef())));
+
+ // Now lets decompress
+
+ List<char> decompressedData;
+ decompressedData.setCount(srcSize);
+
+ SLANG_CHECK(SLANG_SUCCEEDED(system->decompress(compressedBlob->getBufferPointer(), compressedBlob->getBufferSize(), srcSize, decompressedData.getBuffer())));
+ SLANG_CHECK(memcmp(src, decompressedData.getBuffer(), srcSize) == 0);
+ }
+}
diff --git a/tools/slang-unit-test/unit-test-find-type-by-name.cpp b/tools/slang-unit-test/unit-test-find-type-by-name.cpp
new file mode 100644
index 000000000..d12a0d91f
--- /dev/null
+++ b/tools/slang-unit-test/unit-test-find-type-by-name.cpp
@@ -0,0 +1,55 @@
+// unit-test-byte-encode.cpp
+
+#include "../../slang.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "tools/unit-test/slang-unit-test.h"
+
+using namespace Slang;
+
+SLANG_UNIT_TEST(findTypeByName)
+{
+ const char* testSource =
+ "struct TestStruct {"
+ " int member0;"
+ " Texture2D texture1;"
+ "};";
+ auto session = spCreateSession();
+ auto request = spCreateCompileRequest(session);
+ spAddCodeGenTarget(request, SLANG_DXBC);
+ int tuIndex = spAddTranslationUnit(request, SLANG_SOURCE_LANGUAGE_SLANG, "tu1");
+ spAddTranslationUnitSourceString(request, tuIndex, "internalFile", testSource);
+ spCompile(request);
+
+ auto testBody = [&]()
+ {
+ auto reflection = slang::ShaderReflection::get(request);
+ auto testStruct = reflection->findTypeByName("TestStruct");
+ SLANG_CHECK_ABORT(testStruct->getFieldCount() == 2);
+ auto field0Name = testStruct->getFieldByIndex(0)->getName();
+ SLANG_CHECK_ABORT(field0Name != nullptr && strcmp(field0Name, "member0") == 0);
+ auto field1Name = testStruct->getFieldByIndex(1)->getName();
+ SLANG_CHECK_ABORT(field1Name != nullptr && strcmp(field1Name, "texture1") == 0);
+
+ auto intType = reflection->findTypeByName("int");
+ auto intTypeName = intType->getName();
+ SLANG_CHECK_ABORT(intTypeName && strcmp(intTypeName, "int") == 0);
+
+ auto paramBlockType = reflection->findTypeByName("ParameterBlock<TestStruct>");
+ SLANG_CHECK_ABORT(paramBlockType != nullptr);
+ auto paramBlockTypeName = paramBlockType->getName();
+ SLANG_CHECK_ABORT(paramBlockTypeName && strcmp(paramBlockTypeName, "ParameterBlock") == 0);
+ auto paramBlockElementType = paramBlockType->getElementType();
+ SLANG_CHECK_ABORT(paramBlockElementType != nullptr);
+ auto paramBlockElementTypeName = paramBlockElementType->getName();
+ SLANG_CHECK_ABORT(paramBlockElementTypeName && strcmp(paramBlockElementTypeName, "TestStruct") == 0);
+ };
+
+ testBody();
+
+ spDestroyCompileRequest(request);
+ spDestroySession(session);
+}
+
diff --git a/tools/slang-unit-test/unit-test-free-list.cpp b/tools/slang-unit-test/unit-test-free-list.cpp
new file mode 100644
index 000000000..d6c8b47a7
--- /dev/null
+++ b/tools/slang-unit-test/unit-test-free-list.cpp
@@ -0,0 +1,53 @@
+// unit-test-free-list.cpp
+
+#include "../../source/core/slang-free-list.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "tools/unit-test/slang-unit-test.h"
+
+#include "../../source/core/slang-random-generator.h"
+#include "../../source/core/slang-list.h"
+
+using namespace Slang;
+
+SLANG_UNIT_TEST(freeList)
+{
+ FreeList freeList;
+ freeList.init(sizeof(int), sizeof(void*), 10);
+
+ DefaultRandomGenerator randGen(0x24343);
+
+ List<int*> allocs;
+
+ for (int i = 0; i < 1000; i++)
+ {
+ const int numAlloc = randGen.nextInt32UpTo(20);
+
+ for (int j = 0; j < numAlloc; j++)
+ {
+ int* ptr = (int*)freeList.allocate();
+ *ptr = i;
+ allocs.add(ptr);
+ }
+
+ int numDealloc = randGen.nextInt32UpTo(19);
+ numDealloc = int(allocs.getCount()) < numDealloc ? int(allocs.getCount()) : numDealloc;
+
+ for (int j = 0; j < numDealloc; j++)
+ {
+ const int index = randGen.nextInt32UpTo(int(allocs.getCount()));
+
+ int* alloc = allocs[index];
+
+ SLANG_CHECK(*alloc <= i);
+ SLANG_CHECK(*alloc >= 0);
+
+ freeList.deallocate(alloc);
+
+ allocs.fastRemoveAt(index);
+ }
+ }
+}
+
diff --git a/tools/slang-unit-test/unit-test-json.cpp b/tools/slang-unit-test/unit-test-json.cpp
new file mode 100644
index 000000000..2d2874cc8
--- /dev/null
+++ b/tools/slang-unit-test/unit-test-json.cpp
@@ -0,0 +1,320 @@
+
+#include "../../source/compiler-core/slang-json-lexer.h"
+#include "../../source/core/slang-string-escape-util.h"
+#include "../../source/compiler-core/slang-json-parser.h"
+#include "../../source/compiler-core/slang-json-value.h"
+
+#include "tools/unit-test/slang-unit-test.h"
+
+using namespace Slang;
+
+namespace { // anonymous
+
+struct Element
+{
+ JSONTokenType type;
+ const char* value;
+};
+
+} // anonymous
+
+static SlangResult _lex(const char* in, DiagnosticSink* sink, List<JSONToken>& toks)
+{
+ SourceManager* sourceManager = sink->getSourceManager();
+
+ String contents(in);
+ SourceFile* sourceFile = sourceManager->createSourceFileWithString(PathInfo::makeUnknown(), contents);
+ SourceView* sourceView = sourceManager->createSourceView(sourceFile, nullptr, SourceLoc());
+
+ JSONLexer lexer;
+
+ lexer.init(sourceView, sink);
+
+ while (lexer.peekType() != JSONTokenType::EndOfFile)
+ {
+ if (lexer.peekType() == JSONTokenType::Invalid)
+ {
+ toks.add(lexer.peekToken());
+ return SLANG_FAIL;
+ }
+
+ toks.add(lexer.peekToken());
+ lexer.advance();
+ }
+
+ toks.add(lexer.peekToken());
+
+ // If we advance from end of file we should still be at EndOfFile
+ SLANG_ASSERT(lexer.advance() == JSONTokenType::EndOfFile);
+
+ return SLANG_OK;
+}
+
+static SlangResult _parse(const char* in, DiagnosticSink* sink, JSONListener* listener)
+{
+ SourceManager* sourceManager = sink->getSourceManager();
+
+ String contents(in);
+ SourceFile* sourceFile = sourceManager->createSourceFileWithString(PathInfo::makeUnknown(), contents);
+ SourceView* sourceView = sourceManager->createSourceView(sourceFile, nullptr, SourceLoc());
+
+ JSONLexer lexer;
+ lexer.init(sourceView, sink);
+
+ JSONParser parser;
+ SLANG_RETURN_ON_FAIL(parser.parse(&lexer, sourceView, listener, sink));
+ return SLANG_OK;
+}
+
+static bool _areEqual(SourceManager* sourceManager, const List<JSONToken>& toks, const Element* eles, Index elesCount)
+{
+ if (toks.getCount() != elesCount)
+ {
+ return false;
+ }
+
+ SourceView* sourceView = toks.getCount() ? sourceManager->findSourceView(toks[0].loc) : nullptr;
+ const char*const content = sourceView ? sourceView->getContent().begin() : nullptr;
+
+ for (Index i = 0; i < toks.getCount(); ++i)
+ {
+ const JSONToken& tok = toks[i];
+ const auto& ele = eles[i];
+
+ if (tok.type != ele.type)
+ {
+ return false;
+ }
+
+ SLANG_ASSERT(sourceView->getRange().contains(tok.loc));
+
+ const char* start = content + sourceView->getRange().getOffset(tok.loc);
+
+ UnownedStringSlice lexeme(start, tok.length);
+
+ if (lexeme != ele.value)
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+SLANG_UNIT_TEST(json)
+{
+ SourceManager sourceManager;
+ sourceManager.initialize(nullptr, nullptr);
+ DiagnosticSink sink(&sourceManager, nullptr);
+
+ {
+ const char text[] = " { \"Hello\" : [ \"World\", 1, 2.0, -3.0, -435.5345435, 45e-10, 421.00e+20, 17e1] }";
+
+ const Element eles[] =
+ {
+ {JSONTokenType::LBrace, "{" },
+ {JSONTokenType::StringLiteral, "\"Hello\""},
+ {JSONTokenType::Colon, ":" },
+ {JSONTokenType::LBracket, "[" },
+ {JSONTokenType::StringLiteral, "\"World\"" },
+ {JSONTokenType::Comma, "," },
+ {JSONTokenType::IntegerLiteral, "1" },
+ {JSONTokenType::Comma, "," },
+ {JSONTokenType::FloatLiteral, "2.0" },
+ {JSONTokenType::Comma, "," },
+ {JSONTokenType::FloatLiteral, "-3.0" },
+ {JSONTokenType::Comma, "," },
+ {JSONTokenType::FloatLiteral, "-435.5345435" },
+ {JSONTokenType::Comma, "," },
+ {JSONTokenType::FloatLiteral, "45e-10" },
+ {JSONTokenType::Comma, "," },
+ {JSONTokenType::FloatLiteral, "421.00e+20" },
+ {JSONTokenType::Comma, "," },
+ {JSONTokenType::FloatLiteral, "17e1" },
+ {JSONTokenType::RBracket, "]" },
+ {JSONTokenType::RBrace, "}" },
+ {JSONTokenType::EndOfFile, "" },
+ };
+
+ List<JSONToken> toks;
+ SLANG_CHECK(SLANG_SUCCEEDED(_lex(text, &sink, toks)));
+
+ SLANG_CHECK(_areEqual(&sourceManager, toks, eles, SLANG_COUNT_OF(eles)));
+ }
+
+ {
+ StringEscapeHandler* handler = StringEscapeUtil::getHandler(StringEscapeUtil::Style::JSON);
+
+
+ {
+ const auto slice = UnownedStringSlice::fromLiteral("\n\r\b\f\t \"\\/ Some text...");
+
+ SLANG_CHECK(handler->isEscapingNeeded(slice));
+ SLANG_CHECK(!handler->isEscapingNeeded(UnownedStringSlice::fromLiteral("Hello!")));
+
+ StringBuilder escaped;
+ handler->appendEscaped(slice, escaped);
+
+ StringBuilder unescaped;
+ handler->appendUnescaped(escaped.getUnownedSlice(), unescaped);
+
+ SLANG_CHECK(unescaped == slice);
+ }
+
+ {
+ uint32_t v = 0x7f;
+
+ StringBuilder buf;
+ while (v < 0x10000)
+ {
+ char work[10] = "\\u";
+
+ for (Int i = 0; i < 4; ++i)
+ {
+ const uint32_t digitValue = (v >> ((3 - i) * 4)) & 0xf;
+
+ char digitC = (digitValue > 9) ? char(digitValue - 10 + 'a') : char(digitValue + '0');
+ work[i + 2] = digitC;
+ }
+
+ buf << UnownedStringSlice(work, 6);
+
+ v += v;
+ }
+
+ // Decode it
+ StringBuilder unescaped;
+ handler->appendUnescaped(buf.getUnownedSlice(), unescaped);
+
+ // Encode it
+ StringBuilder escaped;
+ handler->appendEscaped(unescaped.getUnownedSlice(), escaped);
+
+ SLANG_CHECK(escaped == buf);
+ }
+ }
+
+ {
+ const char in[] = "{ \"Hello\" : \"Json\", \"!\" : 10, \"array\" : [1, 2, 3.0] }";
+
+ {
+ auto style = JSONWriter::IndentationStyle::Allman;
+
+ JSONWriter writer(style);
+ _parse(in, &sink, &writer);
+
+ JSONWriter writerCheck(style);
+ _parse(writer.getBuilder().getBuffer(), &sink, &writerCheck);
+
+ SLANG_CHECK(writerCheck.getBuilder() == writer.getBuilder());
+ }
+
+ {
+ auto style = JSONWriter::IndentationStyle::KNR;
+
+ JSONWriter writer(style, 80);
+ _parse(in, &sink, &writer);
+
+ JSONWriter writerCheck(style);
+ _parse(writer.getBuilder().getBuffer(), &sink, &writerCheck);
+
+ SLANG_CHECK(writerCheck.getBuilder() == writer.getBuilder());
+ }
+
+ {
+ // Let's parse into a Value
+ RefPtr<JSONContainer> container = new JSONContainer(&sourceManager);
+
+ JSONValue value;
+ {
+ JSONBuilder builder(container);
+
+ SLANG_CHECK(SLANG_SUCCEEDED(_parse(in, &sink, &builder)));
+ value = builder.getRootValue();
+ }
+ // Let's recreate
+ JSONValue copy;
+ {
+ JSONBuilder builder(container);
+ container->traverseRecursively(value, &builder);
+ copy = builder.getRootValue();
+ }
+
+ SLANG_CHECK(container->areEqual(value, copy));
+
+ }
+ }
+
+ {
+ // Only need a SourceManager if we are going to store lexemes
+ RefPtr<JSONContainer> container = new JSONContainer(nullptr);
+
+ {
+ List<JSONValue> values;
+
+ for (Int i = 0; i < 100; ++i)
+ {
+
+ values.add(JSONValue::makeInt(i));
+ values.add(JSONValue::makeFloat(-double(i)));
+ }
+
+ JSONValue array = container->createArray(values.getBuffer(), values.getCount());
+
+ auto arrayView = container->getArray(array);
+
+ SLANG_CHECK(arrayView.getCount() == values.getCount());
+
+ // Check the values are the same
+ SLANG_CHECK(container->areEqual(arrayView.getBuffer(), values.getBuffer(), arrayView.getCount()));
+
+ {
+ JSONWriter writer(JSONWriter::IndentationStyle::KNR, 80);
+
+ container->traverseRecursively(array, &writer);
+ }
+ }
+ {
+ JSONValue obj = JSONValue::makeEmptyObject();
+
+ JSONKey key = container->getKey(UnownedStringSlice::fromLiteral("Hello"));
+
+ container->setKeyValue(obj, key, JSONValue::makeNull());
+ container->setKeyValue(obj, key, JSONValue::makeInt(10));
+
+ auto objView = container->getObject(obj);
+
+ SLANG_CHECK(objView.getCount() == 1);
+
+ SLANG_CHECK(objView[0].value.asInteger() == 10);
+ }
+ }
+
+ // Check repeated keys works out
+ // Check out comparison works with different key orders
+ {
+ RefPtr<JSONContainer> container = new JSONContainer(&sourceManager);
+ const char aText[] = "{ \"a\" : 10, \"b\" : 20.0, \"a\" : \"Hello\" }";
+
+
+ JSONBuilder builder(container);
+ SLANG_CHECK(SLANG_SUCCEEDED(_parse(aText, &sink, &builder)));
+ const JSONValue a = builder.getRootValue();
+
+ builder.reset();
+
+ const char bText[] = "{ \"b\" : 20.0, \"a\" : \"Hello\"}";
+ SLANG_CHECK(SLANG_SUCCEEDED(_parse(bText, &sink, &builder)));
+ const JSONValue b = builder.getRootValue();
+
+ SLANG_CHECK(container->areEqual(a, b));
+
+ JSONBuilder convertBuilder(container, JSONBuilder::Flag::ConvertLexemes);
+
+ SLANG_CHECK(SLANG_SUCCEEDED(_parse(aText, &sink, &convertBuilder)));
+ const JSONValue c = builder.getRootValue();
+
+ SLANG_CHECK(container->areEqual(a, c));
+ }
+}
+
diff --git a/tools/slang-unit-test/unit-test-memory-arena.cpp b/tools/slang-unit-test/unit-test-memory-arena.cpp
new file mode 100644
index 000000000..b2671160a
--- /dev/null
+++ b/tools/slang-unit-test/unit-test-memory-arena.cpp
@@ -0,0 +1,271 @@
+// unit-test-free-list.cpp
+
+#include "../../source/core/slang-memory-arena.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "tools/unit-test/slang-unit-test.h"
+
+#include "../../source/core/slang-random-generator.h"
+#include "../../source/core/slang-list.h"
+
+using namespace Slang;
+
+
+namespace // anonymous
+{
+
+struct Block
+{
+ void* m_data;
+ size_t m_size;
+ uint8_t m_value;
+};
+
+enum class TestMode
+{
+ eUnaligned,
+ eImplicitAligned, ///< Alignment is kept implicitly with Unaligned allocs of the right size
+ eDefaultAligned,
+ eExplicitAligned,
+ eCount,
+};
+
+} // anonymous
+
+static size_t getAlignment(TestMode mode)
+{
+ switch (mode)
+ {
+ default:
+ case TestMode::eUnaligned:
+ return 1;
+ case TestMode::eExplicitAligned:
+ return 16;
+ case TestMode::eImplicitAligned:
+ return 32;
+ case TestMode::eDefaultAligned:
+ return MemoryArena::kMinAlignment;
+ }
+}
+
+static bool hasValueShort(const uint8_t* data, size_t size, uint8_t value)
+{
+ for (size_t i = 0; i < size; ++i)
+ {
+ if (data[i] != value)
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool hasValue(const uint8_t* data, size_t size, uint8_t value)
+{
+ const size_t alignMask = sizeof(size_t) - 1;
+
+ if (size <= sizeof(size_t) * 2)
+ {
+ return hasValueShort(data, size, value);
+ }
+
+ if (size_t(data) & alignMask)
+ {
+ size_t firstSize = sizeof(size_t) - (size_t(data) & alignMask);
+ if (!hasValueShort(data, firstSize, value))
+ {
+ return false;
+ }
+ size -= firstSize;
+ data += firstSize;
+
+ assert((size_t(data) & alignMask) == 0);
+ }
+
+ // Now do the middle
+ size_t numWords = size / sizeof(size_t);
+
+ // Expand the byte up to a word size
+ size_t wordValue = (size_t(value) << 8) | value;
+ wordValue = (wordValue << 16) | wordValue;
+ wordValue = (sizeof(size_t) > 4) ? size_t((uint64_t(wordValue) << 32) | wordValue) : wordValue;
+
+ const size_t* wordData = (const size_t*)data;
+ for (size_t i = 0; i < numWords; ++i)
+ {
+ if (wordData[i] != wordValue)
+ {
+ return false;
+ }
+ }
+
+ // Do the end piece
+ return hasValueShort(data + sizeof(size_t) * numWords, size & alignMask, value);
+}
+
+SLANG_UNIT_TEST(memoryArena)
+{
+ DefaultRandomGenerator randGen(0x5346536a);
+
+ {
+ const size_t blockSize = 1024;
+ MemoryArena arena;
+ arena.init(blockSize);
+
+ List<void*> blocks;
+
+ blocks.add(arena.allocate(100));
+ blocks.add(arena.allocate(blockSize * 2));
+ blocks.add(arena.allocate(100));
+ blocks.add(arena.allocate(blockSize * 2));
+ blocks.add(arena.allocate(100));
+
+ arena.deallocateAll();
+ blocks.add(arena.allocate(100));
+ blocks.add(arena.allocate(blockSize * 2));
+
+ arena.reset();
+
+ {
+ uint32_t data[] = { 1, 2, 3 };
+
+ const uint32_t* copy = arena.allocateAndCopyArray(data, SLANG_COUNT_OF(data));
+
+ SLANG_CHECK(::memcmp(copy, data, sizeof(data)) == 0);
+ }
+ }
+
+ {
+ int count = 0;
+ const size_t blockSize = 1024;
+
+ for (TestMode mode = TestMode(0); int(mode) < int(TestMode::eCount); mode = TestMode(int(mode) + 1))
+ {
+ const size_t alignment = getAlignment(mode);
+
+ MemoryArena arena;
+ arena.init(blockSize, alignment);
+
+ List<Block> blocks;
+
+ for (int i = 0; i < 10000; i++)
+ {
+ count++;
+
+ const int var = randGen.nextInt32() & 0x3ff;
+ if (var < 3 && blocks.getCount() > 0)
+ {
+ if (var == 1)
+ {
+ // Deallocate everything
+ arena.deallocateAll();
+ blocks.clear();
+ }
+ else if (var == 2)
+ {
+ arena.reset();
+ blocks.clear();
+ }
+ else if (var == 3)
+ {
+ arena.rewindToCursor(nullptr);
+ blocks.clear();
+ }
+ else if (var == 4)
+ {
+ // Rewind to a random position
+ int rewindIndex = randGen.nextInt32UpTo(int32_t(blocks.getCount()));
+ // rewind to this block
+ arena.rewindToCursor(blocks[rewindIndex].m_data);
+ // All the blocks (includign this one) and now deallocated
+ blocks.setCount(rewindIndex);
+
+ }
+ else
+ {
+ size_t usedMemory = arena.calcTotalMemoryUsed();
+ size_t allocatedMemory = arena.calcTotalMemoryAllocated();
+
+ SLANG_CHECK(allocatedMemory >= usedMemory);
+ }
+ }
+ else
+ {
+ size_t sizeInBytes = (randGen.nextInt32() & 255) + 1;
+
+ // Lets go for an oversized block
+ if ((randGen.nextInt32() & 0xff) < 2)
+ {
+ sizeInBytes += blockSize;
+ }
+ else if ((randGen.nextInt32() & 0xff) < 2)
+ {
+ // Let's try for a block that's awkwardly sized
+ sizeInBytes = blockSize / 3 + 10;
+ }
+
+ const uint8_t value = uint8_t(randGen.nextInt32());
+
+ void* mem = nullptr;
+ switch (mode)
+ {
+ default:
+ case TestMode::eUnaligned:
+ {
+ mem = arena.allocateUnaligned(sizeInBytes);
+ break;
+ }
+ case TestMode::eImplicitAligned:
+ {
+ // Fix the size to get implicit alignment
+ sizeInBytes = (sizeInBytes & ~(alignment - 1)) + alignment;
+ mem = arena.allocateUnaligned(sizeInBytes);
+ break;
+ }
+ case TestMode::eExplicitAligned:
+ {
+ mem = arena.allocateAligned(sizeInBytes, alignment);
+ break;
+ }
+ case TestMode::eDefaultAligned:
+ {
+ mem = arena.allocate(sizeInBytes);
+ break;
+ }
+ }
+
+ // Check it is aligned
+ SLANG_CHECK((size_t(mem) & (alignment - 1)) == 0);
+
+ ::memset(mem, value, sizeInBytes);
+
+ Block block;
+
+ block.m_data = mem;
+ block.m_size = sizeInBytes;
+ block.m_value = value;
+
+ blocks.add(block);
+ }
+
+ // Check the blocks
+ for (Index j = 0; j < blocks.getCount(); ++j)
+ {
+ const Block& block = blocks[j];
+
+ SLANG_CHECK(arena.isValid(block.m_data, block.m_size));
+
+ SLANG_CHECK(hasValue((uint8_t*)block.m_data, block.m_size, block.m_value));
+ }
+ }
+ }
+ }
+ {
+ // Do lots of allocations and test out rewind
+
+
+
+ }
+}
diff --git a/tools/slang-unit-test/unit-test-path.cpp b/tools/slang-unit-test/unit-test-path.cpp
new file mode 100644
index 000000000..c27feee9c
--- /dev/null
+++ b/tools/slang-unit-test/unit-test-path.cpp
@@ -0,0 +1,62 @@
+// unit-test-path.cpp
+
+#include "../../source/core/slang-io.h"
+
+#include "tools/unit-test/slang-unit-test.h"
+
+using namespace Slang;
+
+SLANG_UNIT_TEST(path)
+{
+#if SLANG_WINDOWS_FAMILY
+ // Disable for now on non windows has some problems on *some* Linux based CI.
+ {
+ String path;
+ SlangResult res = Path::getCanonical("source/slang", path);
+ SLANG_CHECK(SLANG_SUCCEEDED(res));
+
+ String parentPath;
+ res = Path::getCanonical("source", parentPath);
+ SLANG_CHECK(SLANG_SUCCEEDED(res));
+
+ String parentPath2 = Path::getParentDirectory(path);
+ SLANG_CHECK(parentPath == parentPath2);
+ }
+#endif
+ // Test the paths
+ {
+ SLANG_CHECK(Path::simplify(".") == ".");
+ SLANG_CHECK(Path::simplify("..") == "..");
+ SLANG_CHECK(Path::simplify("blah/..") == ".");
+
+ SLANG_CHECK(Path::simplify("blah/.././a") == "a");
+
+ SLANG_CHECK(Path::simplify("a:/what/.././../is/./../this/.") == "a:/../this");
+
+ SLANG_CHECK(Path::simplify("a:/what/.././../is/./../this/./") == "a:/../this");
+
+ SLANG_CHECK(Path::simplify("a:\\what\\..\\.\\..\\is\\.\\..\\this\\.\\") == "a:/../this");
+
+ SLANG_CHECK(Path::simplify("tests/preprocessor/.\\pragma-once-a.h") == "tests/preprocessor/pragma-once-a.h");
+
+
+ SLANG_CHECK(Path::hasRelativeElement("."));
+ SLANG_CHECK(Path::hasRelativeElement(".."));
+ SLANG_CHECK(Path::hasRelativeElement("blah/.."));
+
+ SLANG_CHECK(Path::hasRelativeElement("blah/.././a"));
+ SLANG_CHECK(Path::hasRelativeElement("a") == false);
+ SLANG_CHECK(Path::hasRelativeElement("blah/a") == false);
+ SLANG_CHECK(Path::hasRelativeElement("a:\\blah/a") == false);
+
+
+ SLANG_CHECK(Path::hasRelativeElement("a:/what/.././../is/./../this/."));
+
+ SLANG_CHECK(Path::hasRelativeElement("a:/what/.././../is/./../this/./"));
+
+ SLANG_CHECK(Path::hasRelativeElement("a:\\what\\..\\.\\..\\is\\.\\..\\this\\.\\"));
+
+
+ }
+}
+
diff --git a/tools/slang-unit-test/unit-test-riff.cpp b/tools/slang-unit-test/unit-test-riff.cpp
new file mode 100644
index 000000000..2902a9af5
--- /dev/null
+++ b/tools/slang-unit-test/unit-test-riff.cpp
@@ -0,0 +1,179 @@
+// unit-test-riff.cpp
+
+#include "../../source/core/slang-riff.h"
+
+#include "../../source/core/slang-random-generator.h"
+
+#include "tools/unit-test/slang-unit-test.h"
+
+using namespace Slang;
+
+static void _writeRandom(RandomGenerator* rand, size_t maxSize, RiffContainer& ioContainer, List<uint8_t>& ioData)
+{
+ while (true)
+ {
+ const Index oldCount = ioData.getCount();
+
+ const size_t allocSize = size_t(rand->nextInt32InRange(1, 50));
+
+ if (allocSize + oldCount > maxSize)
+ {
+ break;
+ }
+
+ ioData.setCount(oldCount + Index(allocSize));
+ rand->nextData(ioData.getBuffer() + oldCount, allocSize);
+
+ // Write
+ ioContainer.write(ioData.getBuffer() + oldCount, allocSize);
+ }
+
+ // Should be a single block with same data as the List
+ RiffContainer::DataChunk* dataChunk = as<RiffContainer::DataChunk>(ioContainer.getCurrentChunk());
+ SLANG_ASSERT(dataChunk);
+}
+
+SLANG_UNIT_TEST(riff)
+{
+ typedef RiffContainer::ScopeChunk ScopeChunk;
+ typedef RiffContainer::Chunk::Kind Kind;
+
+ const FourCC markThings = SLANG_FOUR_CC('T', 'H', 'I', 'N');
+ const FourCC markData = SLANG_FOUR_CC('D', 'A', 'T', 'A');
+
+ {
+ RiffContainer container;
+
+ {
+ ScopeChunk scopeContainer(&container, Kind::List, markThings);
+ {
+ ScopeChunk scopeChunk(&container, Kind::Data, markData);
+
+ const char hello[] = "Hello ";
+ const char world[] = "World!";
+
+ container.write(hello, sizeof(hello));
+ container.write(world, sizeof(world));
+ }
+
+ {
+ ScopeChunk scopeChunk(&container, Kind::Data, markData);
+
+ const char test0[] = "Testing... ";
+ const char test1[] = "Testing!";
+
+ container.write(test0, sizeof(test0));
+ container.write(test1, sizeof(test1));
+ }
+
+ {
+ ScopeChunk innerScopeContainer(&container, Kind::List, markThings);
+
+ {
+ ScopeChunk scopeChunk(&container, Kind::Data, markData);
+
+ const char another[] = "Another?";
+ container.write(another, sizeof(another));
+ }
+ }
+ }
+
+ SLANG_CHECK(container.isFullyConstructed());
+ SLANG_CHECK(RiffContainer::isChunkOk(container.getRoot()));
+
+ {
+ StringBuilder builder;
+ {
+ StringWriter writer(&builder, 0);
+ RiffUtil::dump(container.getRoot(), &writer);
+ }
+
+ {
+ OwnedMemoryStream stream(FileAccess::ReadWrite);
+ SLANG_CHECK(SLANG_SUCCEEDED(RiffUtil::write(container.getRoot(), true, &stream)));
+
+ stream.seek(SeekOrigin::Start, 0);
+
+ RiffContainer readContainer;
+ SLANG_CHECK(SLANG_SUCCEEDED(RiffUtil::read(&stream, readContainer)));
+
+ // Dump the read contents
+ StringBuilder readBuilder;
+ {
+ StringWriter writer(&readBuilder, 0);
+ RiffUtil::dump(readContainer.getRoot(), &writer);
+ }
+
+ // They should be the same
+ SLANG_CHECK(readBuilder == builder);
+ }
+ }
+
+ }
+
+ // Test writing as a stream only allocates a single data block (as long as there is enough space).
+ {
+ RiffContainer container;
+
+ ScopeChunk scopeChunk(&container, Kind::List, markData);
+ {
+ ScopeChunk scopeChunk(&container, Kind::Data, markData);
+ RefPtr<RandomGenerator> rand = RandomGenerator::create(0x345234);
+
+ List<uint8_t> data;
+ _writeRandom(rand, container.getMemoryArena().getBlockPayloadSize() / 2, container, data);
+
+ // Should be a single block with same data as the List
+ RiffContainer::DataChunk* dataChunk = as<RiffContainer::DataChunk>(container.getCurrentChunk());
+ SLANG_ASSERT(dataChunk);
+
+ // It should be a single block
+ SLANG_CHECK(dataChunk->getSingleData() != nullptr);
+
+ SLANG_CHECK(dataChunk->isEqual(data.getBuffer(), data.getCount()));
+
+ }
+ }
+
+ // Test writing across multiple data blocks
+ {
+ RefPtr<RandomGenerator> rand = RandomGenerator::create(0x345234);
+
+ for (Int i = 0 ; i < 100; ++i)
+ {
+ RiffContainer container;
+
+ const size_t maxSize = rand->nextInt32InRange(1, int32_t(container.getMemoryArena().getBlockPayloadSize() * 3));
+
+ ScopeChunk scopeChunk(&container, Kind::List, markData);
+ {
+ ScopeChunk scopeChunk(&container, Kind::Data, markData);
+
+ List<uint8_t> data;
+ _writeRandom(rand, maxSize, container, data);
+
+ // Should be a single block with same data as the List
+ RiffContainer::DataChunk* dataChunk = as<RiffContainer::DataChunk>(container.getCurrentChunk());
+ SLANG_CHECK(dataChunk && dataChunk->isEqual(data.getBuffer(), data.getCount()));
+ }
+ }
+ }
+
+#if 0
+ {
+ RiffContainer container;
+ {
+ FileStream readStream("ambient-drop.wav", FileMode::Open, FileAccess::Read, FileShare::ReadWrite);
+ SLANG_CHECK(SLANG_SUCCEEDED(RiffUtil::read(&readStream, container)));
+ RiffUtil::dump(container.getRoot(), StdWriters::getOut());
+ }
+ // Write it
+ {
+
+ FileStream writeStream("check.wav", FileMode::Create, FileAccess::Write, FileShare::ReadWrite);
+ SLANG_CHECK(SLANG_SUCCEEDED(RiffUtil::write(container.getRoot(), true, &writeStream)));
+ }
+ }
+#endif
+}
+
diff --git a/tools/slang-unit-test/unit-test-short-list.cpp b/tools/slang-unit-test/unit-test-short-list.cpp
new file mode 100644
index 000000000..2760d633e
--- /dev/null
+++ b/tools/slang-unit-test/unit-test-short-list.cpp
@@ -0,0 +1,77 @@
+// unit-test-path.cpp
+
+#include "source/core/slang-basic.h"
+#include "tools/unit-test/slang-unit-test.h"
+
+using namespace Slang;
+
+template<typename T>
+static bool _checkArrayView(ArrayView<T> v0, ArrayView<T> v1)
+{
+ if (v0.getCount() != v1.getCount())
+ return false;
+ for (Index i = 0; i < v0.getCount(); i++)
+ if (v0[i] != v1[i])
+ return false;
+ return true;
+}
+
+SLANG_UNIT_TEST(shortList)
+{
+ {
+ ShortList<String, 4> shortList = { "a", "b", "c" };
+ shortList.add("d");
+ auto arrayView = shortList.getArrayView();
+ SLANG_CHECK(arrayView.ownsStorage == false);
+ SLANG_CHECK(_checkArrayView(arrayView.arrayView,
+ List<String>{"a", "b", "c", "d"}.getArrayView()));
+ shortList.add("e");
+ auto arrayView2 = shortList.getArrayView();
+ SLANG_CHECK(arrayView2.ownsStorage == true);
+ SLANG_CHECK(_checkArrayView(arrayView2.arrayView,
+ List<String>{"a", "b", "c", "d", "e"}.getArrayView()));
+ auto arrayView3 = shortList.getArrayView(0, 2);
+ SLANG_CHECK(arrayView3.ownsStorage == false);
+ SLANG_CHECK(_checkArrayView(arrayView3.arrayView,
+ List<String>{"a", "b"}.getArrayView()));
+ auto arrayView4 = shortList.getArrayView(4, 1);
+ SLANG_CHECK(arrayView4.ownsStorage == false);
+ SLANG_CHECK(_checkArrayView(arrayView4.arrayView,
+ List<String>{"e"}.getArrayView()));
+ auto arrayView5 = shortList.getArrayView(2, 3);
+ SLANG_CHECK(arrayView5.ownsStorage == true);
+ SLANG_CHECK(_checkArrayView(arrayView5.arrayView,
+ List<String>{"c", "d", "e"}.getArrayView()));
+
+ ShortList<String, 1> copy2;
+ ShortList<String, 2> copy1;
+ copy1 = shortList;
+ for (auto item : copy1)
+ copy2.add(item);
+ SLANG_CHECK(_checkArrayView(copy2.getArrayView().arrayView,
+ List<String>{"a", "b", "c", "d", "e"}.getArrayView()));
+
+ SLANG_CHECK(copy2.indexOf("a") == 0);
+ SLANG_CHECK(copy2.indexOf("e") == 4);
+
+ SLANG_CHECK(copy2.lastIndexOf("a") == 0);
+ SLANG_CHECK(copy2.lastIndexOf("e") == 4);
+
+ copy2.compress();
+ copy2.add("f");
+ copy2.fastRemove("c");
+ copy2.compress();
+ SLANG_CHECK(_checkArrayView(copy2.getArrayView().arrayView,
+ List<String>{"a", "b", "f", "d", "e"}.getArrayView()));
+
+ shortList.removeLast();
+ shortList.removeLast();
+ shortList.compress();
+ SLANG_CHECK(_checkArrayView(shortList.getArrayView().arrayView,
+ List<String>{"a", "b", "c"}.getArrayView()));
+ shortList.add("d");
+ shortList.add("e");
+ SLANG_CHECK(_checkArrayView(shortList.getArrayView().arrayView,
+ List<String>{"a", "b", "c", "d", "e"}.getArrayView()));
+ }
+}
diff --git a/tools/slang-unit-test/unit-test-string.cpp b/tools/slang-unit-test/unit-test-string.cpp
new file mode 100644
index 000000000..629ed2373
--- /dev/null
+++ b/tools/slang-unit-test/unit-test-string.cpp
@@ -0,0 +1,262 @@
+// unit-test-path.cpp
+
+#include "../../source/core/slang-string-util.h"
+
+#include "tools/unit-test/slang-unit-test.h"
+
+//#include <math.h>
+
+#include <sstream>
+
+using namespace Slang;
+
+static bool _areEqual(const List<UnownedStringSlice>& lines, const UnownedStringSlice* checkLines, Int checkLinesCount)
+{
+ if (checkLinesCount != lines.getCount())
+ {
+ return false;
+ }
+
+ for (Int i = 0; i < checkLinesCount; ++i)
+ {
+ if (lines[i] != checkLines[i])
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool _checkLines(const UnownedStringSlice& input, const UnownedStringSlice* checkLines, Int checkLinesCount)
+{
+ List<UnownedStringSlice> lines;
+ StringUtil::calcLines(input, lines);
+ return _areEqual(lines, checkLines, checkLinesCount);
+}
+
+static bool _checkLineParser(const UnownedStringSlice& input)
+{
+ UnownedStringSlice remaining(input), line;
+ for (const auto parserLine : LineParser(input))
+ {
+ if (!StringUtil::extractLine(remaining, line) || line != parserLine)
+ {
+ return false;
+ }
+ }
+ return StringUtil::extractLine(remaining, line) == false;
+}
+
+static void _append(double v, StringBuilder& buf)
+{
+ std::ostringstream stream;
+ stream.imbue(std::locale::classic());
+ stream.setf(std::ios::fixed, std::ios::floatfield);
+ stream.precision(20);
+
+ stream << std::scientific << v;
+
+ buf << stream.str().c_str();
+}
+
+// Unit of least precision
+static int64_t _calcULPDistance(double a, double b)
+{
+ // Save work if the floats are equal.
+ // Also handles +0 == -0
+ if (a == b)
+ {
+ return 0;
+ }
+
+ const int64_t max = int64_t((~uint64_t(0)) >> 1);
+
+#if 0
+ // Max distance for NaN
+ if (isnan(a) || isnan(b))
+ {
+ return max;
+ }
+
+ // If one's infinite and they're not equal, max distance.
+ if (isinf(a) || isinf(b))
+ {
+ return max;
+ }
+#endif
+
+ int64_t ia, ib;
+ memcpy(&ia, &a, sizeof(a));
+ memcpy(&ib, &b, sizeof(b));
+
+ // Don't compare differently-signed floats.
+ if ((ia < 0) != (ib < 0))
+ {
+ return max;
+ }
+
+ // Return the absolute value of the distance in ULPs.
+ int64_t distance = ia - ib;
+ return distance < 0 ? -distance : distance;
+}
+
+static bool _areApproximatelyEqual(double a, double b, double fixedEpsilon = 1e-10, int ulpsEpsilon = 100)
+{
+ // Handle the near-zero case.
+ const double difference = abs(a - b);
+ if (difference <= fixedEpsilon)
+ {
+ return true;
+ }
+
+ return _calcULPDistance(a, b) <= ulpsEpsilon;
+}
+
+SLANG_UNIT_TEST(string)
+{
+ {
+ UnownedStringSlice checkLines[] = { UnownedStringSlice::fromLiteral("") };
+ SLANG_CHECK(_checkLines(UnownedStringSlice::fromLiteral(""), checkLines, SLANG_COUNT_OF(checkLines)));
+ }
+ {
+ // Will emit no lines
+ SLANG_CHECK(_checkLines(UnownedStringSlice(nullptr, nullptr), nullptr, 0));
+ }
+ {
+ // Two lines - both empty
+ UnownedStringSlice checkLines[] = { UnownedStringSlice(), UnownedStringSlice()};
+ SLANG_CHECK(_checkLines(UnownedStringSlice::fromLiteral("\n"), checkLines, SLANG_COUNT_OF(checkLines)));
+ }
+ {
+ UnownedStringSlice checkLines[] = { UnownedStringSlice::fromLiteral("Hello"), UnownedStringSlice::fromLiteral("World!") };
+ SLANG_CHECK(_checkLines(UnownedStringSlice::fromLiteral("Hello\nWorld!"), checkLines, SLANG_COUNT_OF(checkLines)));
+ }
+ {
+ UnownedStringSlice checkLines[] = { UnownedStringSlice::fromLiteral("Hello"), UnownedStringSlice::fromLiteral("World!"), UnownedStringSlice() };
+ SLANG_CHECK(_checkLines(UnownedStringSlice::fromLiteral("Hello\n\rWorld!\n"), checkLines, SLANG_COUNT_OF(checkLines)));
+ }
+
+ {
+ SLANG_CHECK(_checkLineParser(UnownedStringSlice::fromLiteral("Hello\n\rWorld!\n")));
+ SLANG_CHECK(_checkLineParser(UnownedStringSlice::fromLiteral("\n")));
+ SLANG_CHECK(_checkLineParser(UnownedStringSlice::fromLiteral("")));
+ }
+ {
+ Int value;
+ SLANG_CHECK(SLANG_SUCCEEDED(StringUtil::parseInt(UnownedStringSlice("-10"), value)) && value == -10);
+ SLANG_CHECK(SLANG_SUCCEEDED(StringUtil::parseInt(UnownedStringSlice("0"), value)) && value == 0);
+ SLANG_CHECK(SLANG_SUCCEEDED(StringUtil::parseInt(UnownedStringSlice("-0"), value)) && value == 0);
+
+ SLANG_CHECK(SLANG_SUCCEEDED(StringUtil::parseInt(UnownedStringSlice("13824"), value)) && value == 13824);
+ SLANG_CHECK(SLANG_SUCCEEDED(StringUtil::parseInt(UnownedStringSlice("-13824"), value)) && value == -13824);
+ }
+
+ {
+ UnownedStringSlice values[] = { UnownedStringSlice("hello"), UnownedStringSlice("world"), UnownedStringSlice("!") };
+ ArrayView<UnownedStringSlice> valuesView(values, SLANG_COUNT_OF(values));
+
+ List<UnownedStringSlice> checkValues;
+ StringBuilder builder;
+
+ {
+ builder.Clear();
+ StringUtil::join(values, 0, ',', builder);
+ SLANG_CHECK(builder == "");
+ }
+
+ {
+ builder.Clear();
+ StringUtil::join(values, 1, ',', builder);
+ SLANG_CHECK(builder == "hello");
+
+ StringUtil::split(builder.getUnownedSlice(), ',', checkValues);
+ SLANG_CHECK(checkValues.getArrayView() == ArrayView<UnownedStringSlice>(values, 1));
+ }
+
+ {
+ builder.Clear();
+ StringUtil::join(values, 2, ',', builder);
+ SLANG_CHECK(builder == "hello,world");
+
+ StringUtil::split(builder.getUnownedSlice(), ',', checkValues);
+ SLANG_CHECK(checkValues.getArrayView() == ArrayView<UnownedStringSlice>(values, 2));
+ }
+
+ {
+ builder.Clear();
+ StringUtil::join(values, 3, UnownedStringSlice("ab"), builder);
+ SLANG_CHECK(builder == "helloabworldab!");
+
+ StringUtil::split(builder.getUnownedSlice(), UnownedStringSlice("ab"), checkValues);
+ SLANG_CHECK(checkValues.getArrayView() == ArrayView<UnownedStringSlice>(values, 3));
+ }
+ }
+ {
+
+ List<double> values;
+ values.add(0.0);
+ values.add(-0.0);
+
+ for (Index i = -300; i < 300; ++i)
+ {
+ double value = pow(10, i);
+
+ values.add(value);
+ values.add(-value);
+
+ values.addRange(value / 3);
+ values.addRange(-value / 3);
+ }
+
+ StringBuilder buf;
+
+ for (auto value : values)
+ {
+ buf.Clear();
+ _append(value, buf);
+
+ UnownedStringSlice slice = buf.getUnownedSlice();
+
+ double parsedValue;
+ SlangResult res = StringUtil::parseDouble(slice, parsedValue);
+
+ auto ulpsParsed = _calcULPDistance(value, parsedValue);
+
+ SLANG_CHECK(SLANG_SUCCEEDED(res));
+
+ // Check that they are equal
+ SLANG_CHECK(_areApproximatelyEqual(value, parsedValue));
+ }
+ }
+ {
+ List<int64_t> values;
+ values.add(0);
+
+ for (Index i = 0; i < 63; ++i)
+ {
+ auto value = int64_t(1) << i;
+
+ values.add(value);
+ values.add(-value);
+ }
+
+ StringBuilder buf;
+
+ for (auto value : values)
+ {
+ buf.Clear();
+ buf << value;
+
+
+ int64_t parsedValue;
+
+ UnownedStringSlice slice = buf.getUnownedSlice();
+ SlangResult res = StringUtil::parseInt64(slice, parsedValue);
+
+ SLANG_CHECK(SLANG_SUCCEEDED(res));
+
+ // Check that they are equal
+ SLANG_CHECK(value == parsedValue);
+ }
+ }
+}