summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjsmall-nvidia <jsmall@nvidia.com>2018-09-12 16:27:42 -0400
committerGitHub <noreply@github.com>2018-09-12 16:27:42 -0400
commitf60135cec62c91a9d7923397fe8796d2b3eaa5cb (patch)
tree777646cb3611bf5809dc18e120e506117e143e11
parent9a9733091cc7c9628e445313785d561deb229072 (diff)
Feature/memory arena (#631)
* First pass at MemoryArena. * First pass at RandomGenerator. * Extract TestContext into external source file. * Fix warning on printf. * Use enum classes for Test enums. OutputMode -> TestOutputMode. * First pass at FreeList unit test. * Auto registering tests. Improvements to RandomGenerator. * Remove the need for unitTest headers - cos can use registering. * Added unitTest for MemoryArena. * Do unit tests. * Fix typo.
-rw-r--r--slang.h12
-rw-r--r--source/core/core.vcxproj4
-rw-r--r--source/core/core.vcxproj.filters14
-rw-r--r--source/core/list.h9
-rw-r--r--source/core/slang-memory-arena.cpp244
-rw-r--r--source/core/slang-memory-arena.h393
-rw-r--r--source/core/slang-random-generator.cpp143
-rw-r--r--source/core/slang-random-generator.h97
-rw-r--r--source/core/slang-string-util.cpp48
-rw-r--r--source/core/slang-string-util.h14
-rw-r--r--test.bat1
-rw-r--r--tools/render-test/main.cpp2
-rw-r--r--tools/slang-test/main.cpp712
-rw-r--r--tools/slang-test/slang-test.vcxproj4
-rw-r--r--tools/slang-test/slang-test.vcxproj.filters14
-rw-r--r--tools/slang-test/test-context.cpp450
-rw-r--r--tools/slang-test/test-context.h139
-rw-r--r--tools/slang-test/unit-test-free-list.cpp55
-rw-r--r--tools/slang-test/unit-test-memory-arena.cpp242
19 files changed, 2005 insertions, 592 deletions
diff --git a/slang.h b/slang.h
index 6dcfbb503..564973356 100644
--- a/slang.h
+++ b/slang.h
@@ -230,6 +230,9 @@ convention for interface methods.
# define SLANG_FORCE_INLINE __forceinline
# define SLANG_BREAKPOINT(id) __debugbreak();
# define SLANG_ALIGN_OF(T) __alignof(T)
+
+# define SLANG_INT64(x) (x##i64)
+# define SLANG_UINT64(x) (x##ui64)
#endif // SLANG_MICROSOFT_FAMILY
#ifndef SLANG_FORCE_INLINE
@@ -268,6 +271,14 @@ convention for interface methods.
# define SLANG_UNUSED(v) (void)v;
#endif
+// Used for doing constant literals
+#ifndef SLANG_INT64
+# define SLANG_INT64(x) (x##ll)
+#endif
+#ifndef SLANG_UINT64
+# define SLANG_UINT64(x) (x##ull)
+#endif
+
#ifdef __cplusplus
// C++ specific macros
// Gcc
@@ -300,6 +311,7 @@ convention for interface methods.
# if _MSC_VER >= 1700
# define SLANG_HAS_ENUM_CLASS 1
# endif
+
# endif // SLANG_VC
// Set non set
diff --git a/source/core/core.vcxproj b/source/core/core.vcxproj
index 3dbfaac3f..2944ee9dd 100644
--- a/source/core/core.vcxproj
+++ b/source/core/core.vcxproj
@@ -185,6 +185,8 @@
<ClInclude Include="slang-free-list.h" />
<ClInclude Include="slang-io.h" />
<ClInclude Include="slang-math.h" />
+ <ClInclude Include="slang-memory-arena.h" />
+ <ClInclude Include="slang-random-generator.h" />
<ClInclude Include="slang-string-util.h" />
<ClInclude Include="slang-string.h" />
<ClInclude Include="smart-pointer.h" />
@@ -197,6 +199,8 @@
<ClCompile Include="platform.cpp" />
<ClCompile Include="slang-free-list.cpp" />
<ClCompile Include="slang-io.cpp" />
+ <ClCompile Include="slang-memory-arena.cpp" />
+ <ClCompile Include="slang-random-generator.cpp" />
<ClCompile Include="slang-string-util.cpp" />
<ClCompile Include="slang-string.cpp" />
<ClCompile Include="stream.cpp" />
diff --git a/source/core/core.vcxproj.filters b/source/core/core.vcxproj.filters
index 27b0fe82f..9e90aaa77 100644
--- a/source/core/core.vcxproj.filters
+++ b/source/core/core.vcxproj.filters
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Header Files">
@@ -75,6 +75,12 @@
<ClInclude Include="type-traits.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="slang-memory-arena.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="slang-random-generator.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="platform.cpp">
@@ -101,6 +107,12 @@
<ClCompile Include="token-reader.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="slang-memory-arena.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="slang-random-generator.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="core.natvis">
diff --git a/source/core/list.h b/source/core/list.h
index 6df60b74b..cddcbb6c0 100644
--- a/source/core/list.h
+++ b/source/core/list.h
@@ -199,6 +199,15 @@ namespace Slang
return buffer[_count-1];
}
+ void RemoveLast()
+ {
+#ifdef _DEBUG
+ if (_count == 0)
+ throw "Index out of range.";
+#endif
+ _count--;
+ }
+
inline void SwapWith(List<T, TAllocator> & other)
{
T* tmpBuffer = this->buffer;
diff --git a/source/core/slang-memory-arena.cpp b/source/core/slang-memory-arena.cpp
new file mode 100644
index 000000000..bdfd87602
--- /dev/null
+++ b/source/core/slang-memory-arena.cpp
@@ -0,0 +1,244 @@
+
+#include "slang-memory-arena.h"
+
+namespace Slang {
+
+MemoryArena::MemoryArena()
+{
+ // Mark as invalid so any alloc call will fail
+ m_blockAlignment = 0;
+ m_blockSize = 0;
+
+ // Set up as empty
+ m_blocks = nullptr;
+ _setCurrentBlock(nullptr);
+ m_blockFreeList.init(sizeof(Block), sizeof(void*), 16);
+}
+
+MemoryArena::~MemoryArena()
+{
+ _deallocateBlocks();
+}
+
+MemoryArena::MemoryArena(size_t blockSize, size_t blockAlignment)
+{
+ _initialize(blockSize, blockAlignment);
+}
+
+void MemoryArena::init(size_t blockSize, size_t blockAlignment)
+{
+ _deallocateBlocks();
+ m_blockFreeList.reset();
+ _initialize(blockSize, blockAlignment);
+}
+
+void MemoryArena::_initialize(size_t blockSize, size_t alignment)
+{
+ // Alignment must be a power of 2
+ assert(((alignment - 1) & alignment) == 0);
+
+ // Must be at least sizeof(void*) in size, as that is the minimum the backing allocator will be
+ alignment = (alignment < kMinAlignment) ? kMinAlignment : alignment;
+
+ // If alignment required is larger then the backing allocators then
+ // make larger to ensure when alignment correction takes place it will be aligned
+ if (alignment > kMinAlignment)
+ {
+ blockSize += alignment;
+ }
+
+ m_blockSize = blockSize;
+ m_blockAlignment = alignment;
+ m_blocks = nullptr;
+ _setCurrentBlock(nullptr);
+ m_blockFreeList.init(sizeof(Block), sizeof(void*), 16);
+}
+
+void* MemoryArena::_allocateAligned(size_t size, size_t alignment)
+{
+ assert(size);
+ // Can't be space in the current block -> so we can either place in next, or in a new block
+ _newCurrentBlock(size, alignment);
+ uint8_t* const current = m_current;
+ // If everything has gone to plan, must be space here...
+ assert(current + size <= m_end);
+ m_current = current + size;
+ return current;
+}
+
+void MemoryArena::_deallocateBlocks()
+{
+ Block* currentBlock = m_blocks;
+ while (currentBlock)
+ {
+ // Deallocate the block
+ ::free(currentBlock->m_alloc);
+ // next block
+ currentBlock = currentBlock->m_next;
+ }
+ // Can deallocate all blocks to
+ m_blockFreeList.deallocateAll();
+}
+
+void MemoryArena::_setCurrentBlock(Block* block)
+{
+ if (block)
+ {
+ m_end = block->m_end;
+ m_start = block->m_start;
+ m_current = m_start;
+ }
+ else
+ {
+ m_start = nullptr;
+ m_end = nullptr;
+ m_current = nullptr;
+ }
+ m_currentBlock = block;
+}
+
+MemoryArena::Block* MemoryArena::_newCurrentBlock(size_t size, size_t alignment)
+{
+ // Make sure init has been called (or has been set up in parameterized constructor)
+ assert(m_blockSize > 0);
+ // Alignment must be a power of 2
+ assert(((alignment - 1) & alignment) == 0);
+
+ // Alignment must at a minimum be block alignment (such if reused the constraints hold)
+ alignment = (alignment < m_blockAlignment) ? m_blockAlignment : alignment;
+
+ const size_t alignMask = alignment - 1;
+
+ // First try the next block (if there is one)
+ {
+ Block* next = m_currentBlock ? m_currentBlock->m_next : m_blocks;
+ if (next)
+ {
+ // Align could be done from the actual allocation start, but doing so would mean a pointer which
+ // didn't hit the constraint of being between start/end
+ // So have to align conservatively using start
+ uint8_t* memory = (uint8_t*)((size_t(next->m_start) + alignMask) & ~alignMask);
+
+ // Check if can fit block in
+ if (memory + size <= next->m_end)
+ {
+ _setCurrentBlock(next);
+ return next;
+ }
+ }
+ }
+
+ // The size of the block must be at least large enough to take into account alignment
+ size_t allocSize = (alignment <= kMinAlignment) ? size : (size + alignment);
+
+ // The minimum block size should be at least m_blockSize
+ allocSize = (allocSize < m_blockSize) ? m_blockSize : allocSize;
+
+ // Allocate block
+ Block* block = (Block*)m_blockFreeList.allocate();
+ if (!block)
+ {
+ return nullptr;
+ }
+ // Allocate the memory
+ uint8_t* alloc = (uint8_t*)::malloc(allocSize);
+ if (!alloc)
+ {
+ m_blockFreeList.deallocate(block);
+ return nullptr;
+ }
+ // Do the alignment on the allocation
+ uint8_t* const start = (uint8_t*)((size_t(alloc) + alignMask) & ~alignMask);
+
+ // Setup the block
+ block->m_alloc = alloc;
+ block->m_start = start;
+ block->m_end = alloc + allocSize;
+ block->m_next = nullptr;
+
+ // Insert block into list
+ if (m_currentBlock)
+ {
+ // Insert after current block
+ block->m_next = m_currentBlock->m_next;
+ m_currentBlock->m_next = block;
+ }
+ else
+ {
+ // Add to start of the list of the blocks
+ block->m_next = m_blocks;
+ m_blocks = block;
+ }
+ _setCurrentBlock(block);
+ return block;
+}
+
+MemoryArena::Block* MemoryArena::_findBlock(const void* alloc, Block* endBlock) const
+{
+ const uint8_t* ptr = (const uint8_t*)alloc;
+
+ Block* block = m_blocks;
+ while (block != endBlock)
+ {
+ if (ptr >= block->m_start && ptr < block->m_end)
+ {
+ return block;
+ }
+ block = block->m_next;
+ }
+ return nullptr;
+}
+
+MemoryArena::Block* MemoryArena::_findPreviousBlock(Block* block)
+{
+ Block* currentBlock = m_blocks;
+ while (currentBlock)
+ {
+ if (currentBlock->m_next == block)
+ {
+ return currentBlock;
+ }
+ currentBlock = currentBlock->m_next;
+ }
+ return nullptr;
+}
+
+void MemoryArena::deallocateAll()
+{
+ Block** prev = &m_blocks;
+ Block* block = m_blocks;
+
+ while (block)
+ {
+ if (size_t(block->m_end - block->m_alloc) > m_blockSize)
+ {
+ // Oversized block so we need to free it and remove from the list
+ Block* nextBlock = block->m_next;
+ *prev = nextBlock;
+ // Free the backing memory
+ ::free(block->m_alloc);
+ // Free the block
+ m_blockFreeList.deallocate(block);
+ // prev stays the same, now working on next tho
+ block = nextBlock;
+ }
+ else
+ {
+ // Onto next
+ prev = &block->m_next;
+ block = block->m_next;
+ }
+ }
+
+ // Make the first current (if any)
+ _setCurrentBlock(m_blocks);
+}
+
+void MemoryArena::reset()
+{
+ _deallocateBlocks();
+ m_blocks = nullptr;
+ _setCurrentBlock(nullptr);
+}
+
+} // namespace Slang
diff --git a/source/core/slang-memory-arena.h b/source/core/slang-memory-arena.h
new file mode 100644
index 000000000..acbac51dd
--- /dev/null
+++ b/source/core/slang-memory-arena.h
@@ -0,0 +1,393 @@
+#ifndef SLANG_MEMORY_ARENA_H
+#define SLANG_MEMORY_ARENA_H
+
+#include "../../slang.h"
+
+#include <assert.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "slang-free-list.h"
+
+namespace Slang {
+
+/** Defines arena allocator where allocations are made very quickly, but that deallocations
+can only be performed in reverse order, or with the client code knowing a previous deallocation (say with
+ deallocateAllFrom), automatically deallocates everything after it.
+
+It works by allocating large blocks and then cutting out smaller pieces as requested. If a piece of memory is
+deallocated, it either MUST be in reverse allocation order OR the subsequent allocations are implicitly
+deallocated too, and therefore accessing their memory is now undefined behavior. Allocations are made
+contiguously from the current block. If there is no space in the current block, the
+next block (which is unused) if available is checked. If that works, an allocation is made from the next block.
+If not a new block is allocated that can hold at least the allocation with required alignment.
+
+All memory allocated can be deallocated very quickly and without a client having to track any memory.
+All memory allocated will be freed on destruction - or with reset.
+
+A memory arena can have requests larger than the block size. When that happens they will just be allocated
+from the heap. As such 'oversized blocks' are seen as unusual and potentially wasteful they are deallocated
+when deallocateAll is called, whereas regular size blocks will remain allocated for fast subsequent allocation.
+
+It is intentional that blocks information is stored separately from the allocations that store the
+user data. This is so that alignment permitting, block allocations sizes can be passed directly to underlying allocator.
+For large power of 2 backing allocations this might mean a page/pages directly allocated by the OS for example.
+Also means better cache coherency when traversing blocks -> as generally they will be contiguous in memory.
+
+Also note that allocateUnaligned can be used for slightly faster aligned allocations. All blocks allocated internally
+are aligned to the blockAlignment passed to the constructor. If subsequent allocations (of any type) sizes are of that
+alignment or larger then no alignment fixing is required (because allocations are contiguous) and so 'allocateUnaligned'
+will return allocations of blockAlignment alignment.
+*/
+class MemoryArena
+{
+public:
+ typedef MemoryArena ThisType;
+
+ static const size_t kMinAlignment = sizeof(void*); ///< The minimum alignment of the backing memory allocator.
+
+ /** Determines if an allocation is consistent with an allocation from this arena.
+
+ The test cannot say definitively if this was such an allocation, because the exact details
+ of each allocation is not kept.
+ @param alloc The start of the allocation
+ @param sizeInBytes The size of the allocation
+ @return true if allocation could have been from this Arena */
+ bool isValid(const void* alloc, size_t sizeInBytes) const;
+
+ /** Initialize the arena with specified block size and alignment
+ If the arena has been previously initialized will free and deallocate all memory */
+ void init(size_t blockSize, size_t blockAlignment = kMinAlignment);
+
+ /** Allocate some memory of at least size bytes without having any specific alignment.
+
+ Can be used for slightly faster *aligned* allocations if caveats in class description are met. The
+ Unaligned, means the method will not enforce alignment - but a client call to allocateUnaligned can control
+ subsequent allocations alignments via it's size.
+
+ @param size The size of the allocation requested (in bytes and must be > 0).
+ @return The allocation. Can be nullptr if backing allocator was not able to request required memory */
+ void* allocate(size_t sizeInBytes);
+
+ /** Allocate some aligned memory of at least size bytes
+ @param size Size of allocation wanted (must be > 0).
+ @param alignment Alignment of allocation - must be a power of 2.
+ @return The allocation (or nullptr if unable to allocate). Will be at least 'alignment' alignment or better. */
+ void* allocateAligned(size_t sizeInBytes, size_t alignment);
+
+ /** Allocate some aligned memory of at least size bytes
+ @param size Size of allocation wanted (must be > 0).
+ @param alignment Alignment of allocation - must be a power of 2.
+ @return The allocation (or nullptr if unable to allocate). Will be at least 'alignment' alignment or better. */
+ void* allocateUnaligned(size_t sizeInBytes);
+
+ /** Allocates a null terminated string.
+ @param str A null-terminated string
+ @return A copy of the string held on the arena */
+ const char* allocateString(const char* str);
+
+ /** Allocates a null terminated string.
+ @param chars Pointer to first character
+ @param charCount The amount of characters NOT including terminating 0.
+ @return A copy of the string held on the arena. */
+ const char* allocateString(const char* chars, size_t numChars);
+
+ /// Allocate an element of the specified type. Note: Constructor for type is not executed.
+ template <typename T>
+ T* allocate();
+
+ /// Allocate an array of a specified type. NOTE Constructor of T is NOT executed.
+ template <typename T>
+ T* allocateArray(size_t size);
+
+ /// Allocate an array of a specified type, and copy array passed into it.
+ template <typename T>
+ T* allocateAndCopyArray(const T* src, size_t size);
+
+ /// Allocate an array of a specified type, and zero it.
+ template <typename T>
+ T* allocateAndZeroArray(size_t size);
+
+ /// Deallocate the last allocation. If data is not from the last allocation then the behavior is undefined.
+ void deallocateLast(void* data);
+
+ /// Deallocate this allocation and all remaining after it.
+ void deallocateAllFrom(void* dataStart);
+
+ /** Deallocates all allocated memory. That backing memory will generally not be released so
+ subsequent allocation will be fast, and from the same memory. Note though that 'oversize' blocks
+ will be deallocated. */
+ void deallocateAll();
+
+ /// Resets to the initial state when constructed (and all backing memory will be deallocated)
+ void reset();
+ /// Adjusts such that the next allocate will be at least to the block alignment.
+ void adjustToBlockAlignment();
+
+ /// Gets the block alignment that is passed at initialization otherwise 0 an invalid block alignment.
+ size_t getBlockAlignment() const { return m_blockAlignment; }
+
+ /// Default Ctor
+ MemoryArena();
+ /// Construct with block size and alignment. Block alignment must be a power of 2.
+ MemoryArena(size_t blockSize, size_t blockAlignment = kMinAlignment);
+
+ /// Dtor
+ ~MemoryArena();
+
+protected:
+ struct Block
+ {
+ Block* m_next;
+ uint8_t* m_alloc;
+ uint8_t* m_start;
+ uint8_t* m_end;
+ };
+
+ void _initialize(size_t blockSize, size_t blockAlignment);
+
+ void* _allocateAligned(size_t size, size_t alignment);
+ void _deallocateBlocks();
+
+ void _setCurrentBlock(Block* block);
+
+ Block* _newCurrentBlock(size_t size, size_t alignment);
+ Block* _findBlock(const void* alloc, Block* endBlock = nullptr) const;
+ Block* _findPreviousBlock(Block* block);
+
+ uint8_t* m_start;
+ uint8_t* m_end;
+ uint8_t* m_current;
+ size_t m_blockSize;
+ size_t m_blockAlignment;
+ Block* m_blocks;
+ Block* m_currentBlock;
+
+ FreeList m_blockFreeList;
+
+ private:
+ // Disable
+ MemoryArena(const ThisType& rhs) = delete;
+ void operator=(const ThisType& rhs) = delete;
+};
+
+// --------------------------------------------------------------------------
+inline bool MemoryArena::isValid(const void* data, size_t size) const
+{
+ assert(size);
+
+ uint8_t* ptr = (uint8_t*)data;
+ // Is it in current
+ if (ptr >= m_start && ptr + size <= m_current)
+ {
+ return true;
+ }
+ // Is it in a previous block?
+ Block* block = _findBlock(data, m_currentBlock);
+ return block && (ptr >= block->m_start && (ptr + size) <= block->m_end);
+}
+
+// --------------------------------------------------------------------------
+SLANG_FORCE_INLINE void* MemoryArena::allocateUnaligned(size_t size)
+{
+ // Align with the minimum alignment
+ uint8_t* mem = m_current;
+ uint8_t* end = mem + size;
+ if (end <= m_end)
+ {
+ m_current = end;
+ return mem;
+ }
+ else
+ {
+ return _allocateAligned(size, m_blockAlignment);
+ }
+}
+
+// --------------------------------------------------------------------------
+SLANG_FORCE_INLINE void* MemoryArena::allocate(size_t size)
+{
+ // Align with the minimum alignment
+ const size_t alignMask = kMinAlignment - 1;
+ uint8_t* mem = (uint8_t*)((size_t(m_current) + alignMask) & ~alignMask);
+
+ if (mem + size <= m_end)
+ {
+ m_current = mem + size;
+ return mem;
+ }
+ else
+ {
+ return _allocateAligned(size, kMinAlignment);
+ }
+}
+
+// --------------------------------------------------------------------------
+inline void* MemoryArena::allocateAligned(size_t size, size_t alignment)
+{
+ // Alignment must be a power of 2
+ assert(((alignment - 1) & alignment) == 0);
+
+ // Align the pointer
+ const size_t alignMask = alignment - 1;
+ uint8_t* memory = (uint8_t*)((size_t(m_current) + alignMask) & ~alignMask);
+
+ if (memory + size <= m_end)
+ {
+ m_current = memory + size;
+ return memory;
+ }
+ else
+ {
+ return _allocateAligned(size, alignment);
+ }
+}
+
+// --------------------------------------------------------------------------
+inline const char* MemoryArena::allocateString(const char* str)
+{
+ size_t size = ::strlen(str);
+ if (size == 0)
+ {
+ return "";
+ }
+ char* dst = (char*)allocateUnaligned(size + 1);
+ ::memcpy(dst, str, size + 1);
+ return dst;
+}
+
+// --------------------------------------------------------------------------
+inline const char* MemoryArena::allocateString(const char* chars, size_t charsCount)
+{
+ if (charsCount == 0)
+ {
+ return "";
+ }
+ char* dst = (char*)allocateUnaligned(charsCount + 1);
+ ::memcpy(dst, chars, charsCount);
+
+ // Add null-terminating zero
+ dst[charsCount] = 0;
+ return dst;
+}
+
+// --------------------------------------------------------------------------
+template <typename T>
+inline T* MemoryArena::allocate()
+{
+ return reinterpret_cast<T*>(allocateAligned(sizeof(T), SLANG_ALIGN_OF(T)));
+}
+
+// --------------------------------------------------------------------------
+template <typename T>
+inline T* MemoryArena::allocateArray(size_t count)
+{
+ return (count > 0) ? reinterpret_cast<T*>(allocateAligned(sizeof(T) * count, SLANG_ALIGN_OF(T))) : nullptr;
+}
+
+// --------------------------------------------------------------------------
+template <typename T>
+inline T* MemoryArena::allocateAndCopyArray(const T* arr, size_t size)
+{
+ if (size > 0)
+ {
+ const size_t totalSize = sizeof(T) * size;
+ void* ptr = allocateAligned(totalSize, SLANG_ALIGN_OF(T));
+ ::memcpy(ptr, arr, totalSize);
+ return reinterpret_cast<T*>(ptr);
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+template <typename T>
+inline T* MemoryArena::allocateAndZeroArray(size_t size)
+{
+ if (size > 0)
+ {
+ const size_t totalSize = sizeof(T) * size;
+ void* ptr = allocateAligned(totalSize, SLANG_ALIGN_OF(T));
+ ::memset(ptr, 0, totalSize);
+ return reinterpret_cast<T*>(ptr);
+ }
+ return nullptr;
+}
+
+// --------------------------------------------------------------------------
+inline void MemoryArena::deallocateLast(void* data)
+{
+ // See if it's in current block
+ uint8_t* ptr = (uint8_t*)data;
+ if (ptr >= m_start && ptr < m_current)
+ {
+ // Then just go back
+ m_current = ptr;
+ }
+ else
+ {
+ // Only called if not in the current block. Therefore can only be in previous
+ Block* prevBlock = _findPreviousBlock(m_currentBlock);
+ if (prevBlock == nullptr || (!(ptr >= prevBlock->m_start && ptr < prevBlock->m_end)))
+ {
+ assert(!"Allocation not found");
+ return;
+ }
+
+ // Make the previous block the current
+ _setCurrentBlock(prevBlock);
+ // Make the current the alloc freed
+ m_current = ptr;
+ }
+}
+
+// --------------------------------------------------------------------------
+inline void MemoryArena::deallocateAllFrom(void* data)
+{
+ // See if it's in current block, and is allocated (ie < m_current)
+ uint8_t* ptr = (uint8_t*)data;
+ if (ptr >= m_start && ptr < m_current)
+ {
+ // If it's in current block, then just go back
+ m_current = ptr;
+ return;
+ }
+
+ // Search all blocks prior to current block
+ Block* block = _findBlock(data, m_currentBlock);
+ assert(block);
+ if (!block)
+ {
+ return;
+ }
+ // Make this current block
+ _setCurrentBlock(block);
+
+ // Move the pointer to the allocations position
+ m_current = ptr;
+}
+
+// --------------------------------------------------------------------------
+inline void MemoryArena::adjustToBlockAlignment()
+{
+ const size_t alignMask = m_blockAlignment - 1;
+ uint8_t* ptr = (uint8_t*)((size_t(m_current) + alignMask) & ~alignMask);
+
+ // Alignment might push beyond end of block... if so allocate a new block
+ // This test could be avoided if we aligned m_end, but depending on block alignment that might waste some space
+ if (ptr > m_end)
+ {
+ // We'll need a new block to make this alignment
+ _newCurrentBlock(0, m_blockAlignment);
+ }
+ else
+ {
+ // Set the position
+ m_current = ptr;
+ }
+ assert(size_t(m_current) & alignMask);
+}
+
+} // namespace Slang
+
+#endif // SLANG_MEMORY_ARENA_H \ No newline at end of file
diff --git a/source/core/slang-random-generator.cpp b/source/core/slang-random-generator.cpp
new file mode 100644
index 000000000..7e8476c30
--- /dev/null
+++ b/source/core/slang-random-generator.cpp
@@ -0,0 +1,143 @@
+
+#include "slang-random-generator.h"
+
+namespace Slang {
+
+/* !!!!!!!!!!!!!!!!!!!!!!!!!!!! RandomGenerator !!!!!!!!!!!!!!!!!!!!!!!! */
+
+float RandomGenerator::nextUnitFloat32()
+{
+ int32_t intValue = nextInt32();
+ return (intValue & 0x7fffffff) * (1.0f / 0x7fffffff);
+}
+
+bool RandomGenerator::nextBool()
+{
+ uint32_t bits = uint32_t(nextInt32());
+
+ // Xor together all bits in each byte
+ bits = ((bits & 0xaaaaaaaa) >> 1) ^ (bits & 0x55555555);
+ bits = ((bits & 0x44444444) >> 2) ^ (bits & 0x11111111);
+ bits = ((bits & 0x10101010) >> 4) ^ (bits & 0x01010101);
+
+ // In effect is the xor of all the bits of the original last byte
+ return ( bits & 1) != 0;
+}
+
+int64_t RandomGenerator::nextInt64()
+{
+ const int32_t high = nextInt32();
+ const int32_t low = nextInt32();
+
+ return (int64_t(high) << 32) | low;
+}
+
+int32_t RandomGenerator::nextInt32InRange(int32_t min, int32_t max)
+{
+ int32_t diff = max - min;
+ if (diff <= 1)
+ {
+ return min;
+ }
+
+ return (nextPositiveInt32() % diff) + min;
+}
+
+int64_t RandomGenerator::nextInt64InRange(int64_t min, int64_t max)
+{
+ int64_t diff = max - min;
+ if (diff <= 1)
+ {
+ return min;
+ }
+ return (nextPositiveInt64() % diff) + min;
+}
+
+/* static */RandomGenerator* RandomGenerator::create(int32_t seed)
+{
+ return new DefaultRandomGenerator(seed);
+}
+
+/* !!!!!!!!!!!!!!!!!!!!!!!!!!!! Mt19937RandomGenerator !!!!!!!!!!!!!!!!!!!!!!!! */
+
+Mt19937RandomGenerator::Mt19937RandomGenerator()
+{
+ reset(21452);
+}
+
+Mt19937RandomGenerator::Mt19937RandomGenerator(const ThisType& rhs)
+{
+ *this = rhs;
+}
+
+Mt19937RandomGenerator::Mt19937RandomGenerator(int32_t seed)
+{
+ reset(seed);
+}
+
+void Mt19937RandomGenerator::_generate()
+{
+ const uint32_t xorValue = 2567483615u;
+ for (int i = 0; i < kNumEntries - 1; ++i)
+ {
+ const uint32_t y = (m_mt[i] & 0x80000000) + (m_mt[i + 1] & 0x7fffffff);
+
+ // o = (i + 397) % kNumEntries
+ int32_t o = i + 397;
+ o = (o >= kNumEntries) ? (o - kNumEntries) : o;
+
+ m_mt[i] = m_mt[o] ^ (y >> 1);
+ // If y is odd
+ if (y & 1)
+ {
+ m_mt[i] = m_mt[i] ^ xorValue;
+ }
+ }
+
+ // Last
+ {
+ const int i = kNumEntries - 1;
+ const uint32_t y = (m_mt[i] & 0x80000000) + (m_mt[0] & 0x7fffffff);
+ const int32_t o = ((i + 397) - kNumEntries);
+
+ m_mt[i] = m_mt[o] ^ (y >> 1);
+ // If y is odd
+ if (y & 1)
+ {
+ m_mt[i] = m_mt[i] ^ xorValue;
+ }
+ }
+
+ m_index = 0;
+}
+
+void Mt19937RandomGenerator::reset(int32_t seedIn)
+{
+ m_index = 0;
+ m_mt[0] = uint32_t(seedIn);
+ for (int i = 1; i < kNumEntries; ++i)
+ {
+ m_mt[i] = (1812433253 * (m_mt[i - 1] ^ (m_mt[i - 1] >> 30)) + i);
+ }
+}
+
+int32_t Mt19937RandomGenerator::nextInt32()
+{
+ if (m_index >= kNumEntries)
+ {
+ _generate();
+ }
+
+ uint32_t y = m_mt[m_index++];
+ y = y ^ (y >> 11);
+ y = y ^ ((y << 7) & uint32_t(0x9d2c5680u));
+ y = y ^ ((y << 15) & uint32_t(0xefc6000u));
+ y = y ^ (y >> 18);
+
+ return int32_t(y);
+}
+
+
+
+
+} // namespace Slang
diff --git a/source/core/slang-random-generator.h b/source/core/slang-random-generator.h
new file mode 100644
index 000000000..7b3e42288
--- /dev/null
+++ b/source/core/slang-random-generator.h
@@ -0,0 +1,97 @@
+#ifndef SLANG_RANDOM_GENERATOR_H
+#define SLANG_RANDOM_GENERATOR_H
+
+#include "../../slang.h"
+
+#include <assert.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "smart-pointer.h"
+
+namespace Slang {
+
+class RandomGenerator: public RefObject
+{
+ public:
+
+ /// Make a copy of the generator in the same state
+ virtual RandomGenerator* clone() = 0;
+
+ /// Reset with a seed
+ virtual void reset(int32_t seed) = 0;
+ /// Next int32_t random number
+ virtual int32_t nextInt32() = 0;
+ /// Next int64_t random number
+ virtual int64_t nextInt64();
+
+ /// Get a 0-1 range floating point
+ virtual float nextUnitFloat32();
+
+ /// Get the next bool
+ virtual bool nextBool();
+
+ /// Next Int32 which can only be positive
+ int32_t nextPositiveInt32() { return nextInt32() & 0x7fffffff; }
+ /// Next Int64 which can only be positive
+ int64_t nextPositiveInt64() { return nextInt64() & SLANG_INT64(0x7fffffffffffffff); }
+
+ /// Returns value up to BUT NOT INCLUDING maxValue.
+ int32_t nextInt32UpTo(int32_t maxValue) { assert(maxValue > 0); return (maxValue <= 1) ? 0 : (nextPositiveInt32() % maxValue); }
+
+ /// Returns value from min up to BUT NOT INCLUDING max
+ int32_t nextInt32InRange(int32_t min, int32_t max);
+
+ /// Returns value up to BUT NOT INCLUDING maxValue
+ int64_t nextInt64UpTo(int64_t maxValue) { assert(maxValue > 0); return (maxValue <= 1) ? 0 : (nextPositiveInt64() % maxValue); }
+
+ /// Returns value from min up to BUT NOT INCLUDING max
+ int64_t nextInt64InRange(int64_t min, int64_t max);
+
+ /// Create a RandomGenerator with specified seed using default generator type
+ static RandomGenerator* create(int32_t seed);
+};
+
+/* Mersenne Twister random number generator
+https://en.wikipedia.org/wiki/Mersenne_Twister
+*/
+class Mt19937RandomGenerator: public RandomGenerator
+{
+ public:
+ typedef Mt19937RandomGenerator ThisType;
+
+ enum
+ {
+ kNumEntries = 624
+ };
+
+ Mt19937RandomGenerator* clone() SLANG_OVERRIDE { return new ThisType(*this); }
+ void reset(int32_t seed) SLANG_OVERRIDE;
+ int32_t nextInt32() SLANG_OVERRIDE;
+
+ /// Ctor
+ Mt19937RandomGenerator();
+ Mt19937RandomGenerator(const ThisType& rhs);
+ explicit Mt19937RandomGenerator(int32_t seed);
+
+ /// Assignment
+ void operator=(const ThisType& rhs)
+ {
+ m_index = rhs.m_index;
+ ::memcpy(m_mt, rhs.m_mt, sizeof(m_mt));
+ }
+
+ protected:
+ void _generate();
+
+ uint32_t m_mt[kNumEntries]; ///< The random state vector
+ int m_index; ///< If set to >= kMaxEntries it means reset
+
+};
+
+typedef Mt19937RandomGenerator DefaultRandomGenerator;
+
+} // namespace Slang
+
+#endif // SLANG_RANDOM_GENERATOR_H \ No newline at end of file
diff --git a/source/core/slang-string-util.cpp b/source/core/slang-string-util.cpp
index c60ad9683..29f8dc0ca 100644
--- a/source/core/slang-string-util.cpp
+++ b/source/core/slang-string-util.cpp
@@ -26,4 +26,52 @@ namespace Slang {
}
}
+
+/* static */void StringUtil::append(const char* format, va_list args, StringBuilder& buf)
+{
+ int numChars = 0;
+
+#if SLANG_WINDOWS_FAMILY
+ numChars = _vscprintf(format, args);
+#else
+ {
+ va_list argsCopy;
+ va_copy(argsCopy, args);
+ numChars = vsnprintf(nullptr, 0, format, argsCopy);
+ va_end(argsCopy);
+ }
+#endif
+
+ List<char> chars;
+ chars.SetSize(numChars + 1);
+
+#if SLANG_WINDOWS_FAMILY
+ vsnprintf_s(chars.Buffer(), numChars + 1, _TRUNCATE, format, args);
+#else
+ vsnprintf(chars.Buffer(), numChars + 1, format, args);
+#endif
+
+ buf.Append(chars.Buffer(), numChars);
+}
+
+/* static */void StringUtil::appendFormat(StringBuilder& buf, const char* format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ append(format, args, buf);
+ va_end(args);
+}
+
+/* static */String StringUtil::makeStringWithFormat(const char* format, ...)
+{
+ StringBuilder builder;
+
+ va_list args;
+ va_start(args, format);
+ append(format, args, builder);
+ va_end(args);
+
+ return builder;
+}
+
} // namespace Slang
diff --git a/source/core/slang-string-util.h b/source/core/slang-string-util.h
index bae576370..fc3258490 100644
--- a/source/core/slang-string-util.h
+++ b/source/core/slang-string-util.h
@@ -4,14 +4,26 @@
#include "slang-string.h"
#include "list.h"
+#include <stdarg.h>
+
namespace Slang {
struct StringUtil
{
+ /// Split in, by specified splitChar into slices out
+ /// Slices contents will directly address into in, so contents will only stay valid as long as in does.
static void split(const UnownedStringSlice& in, char splitChar, List<UnownedStringSlice>& slicesOut);
+
+ /// Appends formatted string with args into buf
+ static void append(const char* format, va_list args, StringBuilder& buf);
+
+ /// Appends the formatted string with specified trailing args
+ static void appendFormat(StringBuilder& buf, const char* format, ...);
+
+ /// Create a string from the format string applying args (like sprintf)
+ static String makeStringWithFormat(const char* format, ...);
};
} // namespace Slang
-
#endif // SLANG_STRING_UTIL_H
diff --git a/test.bat b/test.bat
index 6c6e88580..f21dcbbfb 100644
--- a/test.bat
+++ b/test.bat
@@ -56,3 +56,4 @@ SET "PATH=%PATH%;%SLANG_TEST_BIN_DIR%"
:: TODO: Maybe we should actually invoke `msbuild` to make sure all the code is up to date?
"%SLANG_TEST_BIN_DIR%slang-test.exe" -bindir "%SLANG_TEST_BIN_DIR%\" %*
+"%SLANG_TEST_BIN_DIR%slang-test.exe" -bindir "%SLANG_TEST_BIN_DIR%\" -unitTests
diff --git a/tools/render-test/main.cpp b/tools/render-test/main.cpp
index 4734c2c8f..77e077b6c 100644
--- a/tools/render-test/main.cpp
+++ b/tools/render-test/main.cpp
@@ -338,7 +338,7 @@ Result RenderTestApp::writeBindingOutput(const char* fileName)
}
else
{
- printf("invalid output type at %d.\n", i);
+ printf("invalid output type at %d.\n", int(i));
}
}
}
diff --git a/tools/slang-test/main.cpp b/tools/slang-test/main.cpp
index 49279e81c..b57dc6d9c 100644
--- a/tools/slang-test/main.cpp
+++ b/tools/slang-test/main.cpp
@@ -11,6 +11,7 @@ using namespace Slang;
#include "os.h"
#include "render-api-util.h"
+#include "test-context.h"
#define STB_IMAGE_IMPLEMENTATION
#include "external/stb/stb_image.h"
@@ -26,98 +27,6 @@ using namespace Slang;
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
-enum OutputMode
-{
- // Default mode is to write test results to the console
- kOutputMode_Default = 0,
-
- // When running under AppVeyor continuous integration, we
- // need to output test results in a way that the AppVeyor
- // environment can pick up and display.
- kOutputMode_AppVeyor,
-
- // We currently don't specialize for Travis, but maybe
- // we should.
- kOutputMode_Travis,
-
- // xUnit original format
- // https://nose.readthedocs.io/en/latest/plugins/xunit.html
- kOutputMode_xUnit,
-
- // https://xunit.github.io/docs/format-xml-v2
- kOutputMode_xUnit2,
-};
-
-enum TestResult
-{
- kTestResult_Fail,
- kTestResult_Pass,
- kTestResult_Ignored,
-};
-
-enum class MessageType
-{
- INFO, ///< General info (may not be shown depending on verbosity setting)
- TEST_FAILURE, ///< Describes how a test failure took place
- RUN_ERROR, ///< Describes an error that caused a test not to actually correctly run
-};
-
-struct TestContext
-{
- struct TestInfo
- {
- TestResult testResult = TestResult::kTestResult_Ignored;
- String name;
- String message; ///< Message that is specific for the testResult
- };
-
- void addResult(const String& testName, TestResult testResult);
-
- void startTest(const String& testName);
- TestResult endTest(TestResult result);
-
- // Called for an error in the test-runner (not for an error involving
- // a test itself).
- void message(MessageType type, const String& errorText);
- void messageFormat(MessageType type, char const* message, ...);
-
- void dumpOutputDifference(const String& expectedOutput, const String& actualOutput);
-
- bool canWriteStdError() const
- {
- switch (m_outputMode)
- {
- case kOutputMode_xUnit:
- case kOutputMode_xUnit2:
- {
- return false;
- }
- default: return true;
- }
- }
-
- /// Ctor
- TestContext(OutputMode outputMode);
-
- List<TestInfo> m_testInfos;
-
- int m_totalTestCount;
- int m_passedTestCount;
- int m_failedTestCount;
- int m_ignoredTestCount;
-
- OutputMode m_outputMode = kOutputMode_Default;
- bool m_dumpOutputOnFailure;
- bool m_isVerbose;
-
- protected:
- void _addResult(const TestInfo& info);
-
- StringBuilder m_currentMessage;
-
- TestInfo m_currentInfo;
- bool m_inTest;
-};
// A category that a test can be tagged with
struct TestCategory
@@ -190,7 +99,7 @@ struct Options
bool dumpOutputOnFailure = false;
// kind of output to generate
- OutputMode outputMode = kOutputMode_Default;
+ TestOutputMode outputMode = TestOutputMode::eDefault;
// Only run tests that match one of the given categories
Dictionary<TestCategory*, TestCategory*> includeCategories;
@@ -204,6 +113,9 @@ struct Options
// By default we potentially synthesize test for all
// TODO: Vulkan is disabled by default for now as the majority as vulkan synthesized tests fail
RenderApiFlags synthesizedTestApis = RenderApiFlag::AllOf & ~RenderApiFlag::Vulkan;
+
+ // Set this to turn on unit tests
+ bool unitTests = false;
};
// Globals
@@ -216,271 +128,8 @@ TestCategory* g_defaultTestCategory;
TestCategory* findTestCategory(String const& name);
-void append(const char* format, va_list args, StringBuilder& buf);
-
-void appendFormat(StringBuilder& buf, const char* format, ...);
-String makeStringWithFormat(const char* format, ...);
-
-/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!! TestContext !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
-
-TestContext::TestContext(OutputMode outputMode):
- m_outputMode(outputMode)
-{
- m_totalTestCount = 0;
- m_passedTestCount = 0;
- m_failedTestCount = 0;
- m_ignoredTestCount = 0;
-
- m_inTest = false;
- m_dumpOutputOnFailure = false;
- m_isVerbose = false;
-}
-
-void TestContext::startTest(const String& testName)
-{
- assert(!m_inTest);
- m_inTest = true;
-
- m_currentInfo = TestInfo();
- m_currentInfo.name = testName;
- m_currentMessage.Clear();
-}
-
-TestResult TestContext::endTest(TestResult result)
-{
- assert(m_inTest);
-
- m_currentInfo.testResult = result;
- m_currentInfo.message = m_currentMessage;
-
- _addResult(m_currentInfo);
-
- m_inTest = false;
-
- return result;
-}
-
-void TestContext::dumpOutputDifference(const String& expectedOutput, const String& actualOutput)
-{
- StringBuilder builder;
-
- appendFormat(builder,
- "ERROR:\n"
- "EXPECTED{{{\n%s}}}\n"
- "ACTUAL{{{\n%s}}}\n",
- expectedOutput.Buffer(),
- actualOutput.Buffer());
-
-
- if (m_dumpOutputOnFailure && canWriteStdError())
- {
- fprintf(stderr, "%s", builder.Buffer());
- fflush(stderr);
- }
-
- // Add to the m_currentInfo
- message(MessageType::TEST_FAILURE, builder);
-}
-
-void TestContext::_addResult(const TestInfo& info)
-{
- m_totalTestCount++;
-
- switch (info.testResult)
- {
- case kTestResult_Fail:
- m_failedTestCount++;
- break;
-
- case kTestResult_Pass:
- m_passedTestCount++;
- break;
-
- case kTestResult_Ignored:
- m_ignoredTestCount++;
- break;
-
- default:
- assert(!"unexpected");
- break;
- }
-
- m_testInfos.Add(info);
-
- // printf("OUTPUT_MODE: %d\n", options.outputMode);
- switch (m_outputMode)
- {
- default:
- {
- char const* resultString = "UNEXPECTED";
- switch (info.testResult)
- {
- case kTestResult_Fail: resultString = "FAILED"; break;
- case kTestResult_Pass: resultString = "passed"; break;
- case kTestResult_Ignored: resultString = "ignored"; break;
- default:
- assert(!"unexpected");
- break;
- }
- printf("%s test: '%S'\n", resultString, info.name.ToWString().begin());
- break;
- }
- case kOutputMode_xUnit2:
- case kOutputMode_xUnit:
- {
- // Don't output anything -> we'll output all in one go at the end
- break;
- }
- case kOutputMode_AppVeyor:
- {
- char const* resultString = "None";
- switch (info.testResult)
- {
- case kTestResult_Fail: resultString = "Failed"; break;
- case kTestResult_Pass: resultString = "Passed"; break;
- case kTestResult_Ignored: resultString = "Ignored"; break;
- default:
- assert(!"unexpected");
- break;
- }
-
- OSProcessSpawner spawner;
- spawner.pushExecutableName("appveyor");
- spawner.pushArgument("AddTest");
- spawner.pushArgument(info.name);
- spawner.pushArgument("-FileName");
- // TODO: this isn't actually a file name in all cases
- spawner.pushArgument(info.name);
- spawner.pushArgument("-Framework");
- spawner.pushArgument("slang-test");
- spawner.pushArgument("-Outcome");
- spawner.pushArgument(resultString);
-
- auto err = spawner.spawnAndWaitForCompletion();
-
- if (err != kOSError_None)
- {
- messageFormat(MessageType::INFO, "failed to add appveyor test results for '%S'\n", info.name.ToWString().begin());
-
-#if 0
- fprintf(stderr, "[%d] TEST RESULT: %s {%d} {%s} {%s}\n", err, spawner.commandLine_.Buffer(),
- spawner.getResultCode(),
- spawner.getStandardOutput().begin(),
- spawner.getStandardError().begin());
-#endif
- }
-
- break;
- }
- }
-}
-
-void TestContext::addResult(const String& testName, TestResult testResult)
-{
- // Can't add this way if in test
- assert(!m_inTest);
-
- TestInfo info;
- info.name = testName;
- info.testResult = testResult;
- _addResult(info);
-}
-
-void TestContext::message(MessageType type, const String& message)
-{
- if (type == MessageType::INFO)
- {
- if (m_isVerbose && canWriteStdError())
- {
- fputs(message.Buffer(), stderr);
- }
-
- // Just dump out if can dump out
- return;
- }
-
- if (canWriteStdError())
- {
- if (type == MessageType::RUN_ERROR || type == MessageType::TEST_FAILURE)
- {
- fprintf(stderr, "error: ");
- fputs(message.Buffer(), stderr);
- fprintf(stderr, "\n");
- }
- else
- {
- fputs(message.Buffer(), stderr);
- }
- }
-
- if (m_currentMessage.Length() > 0)
- {
- m_currentMessage << "\n";
- }
- m_currentMessage.Append(message);
-}
-
-void TestContext::messageFormat(MessageType type, char const* format, ...)
-{
- StringBuilder builder;
-
- va_list args;
- va_start(args, format);
- append(format, args, builder);
- va_end(args);
-
- message(type, builder);
-}
-
/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!! Functions !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
-void append(const char* format, va_list args, StringBuilder& buf)
-{
- int numChars = 0;
-
-#if SLANG_WINDOWS_FAMILY
- numChars = _vscprintf(format, args);
-#else
- {
- va_list argsCopy;
- va_copy(argsCopy, args);
- numChars = vsnprintf(nullptr, 0, format, argsCopy);
- va_end(argsCopy);
- }
-#endif
-
- List<char> chars;
- chars.SetSize(numChars + 1);
-
-#if SLANG_WINDOWS_FAMILY
- vsnprintf_s(chars.Buffer(), numChars + 1, _TRUNCATE, format, args);
-#else
- vsnprintf(chars.Buffer(), numChars + 1, format, args);
-#endif
-
- buf.Append(chars.Buffer(), numChars);
-}
-
-void appendFormat(StringBuilder& buf, const char* format, ...)
-{
- va_list args;
- va_start(args, format);
- append(format, args, buf);
- va_end(args);
-}
-
-String makeStringWithFormat(const char* format, ...)
-{
- StringBuilder builder;
-
- va_list args;
- va_start(args, format);
- append(format, args, builder);
- va_end(args);
-
- return builder;
-}
-
Result parseOptions(int* argc, char** argv)
{
@@ -563,21 +212,21 @@ Result parseOptions(int* argc, char** argv)
}
else if( strcmp(arg, "-appveyor") == 0 )
{
- g_options.outputMode = kOutputMode_AppVeyor;
+ g_options.outputMode = TestOutputMode::eAppVeyor;
g_options.dumpOutputOnFailure = true;
}
else if( strcmp(arg, "-travis") == 0 )
{
- g_options.outputMode = kOutputMode_Travis;
+ g_options.outputMode = TestOutputMode::eTravis;
g_options.dumpOutputOnFailure = true;
}
else if (strcmp(arg, "-xunit") == 0)
{
- g_options.outputMode = kOutputMode_xUnit;
+ g_options.outputMode = TestOutputMode::eXUnit;
}
else if (strcmp(arg, "-xunit2") == 0)
{
- g_options.outputMode = kOutputMode_xUnit2;
+ g_options.outputMode = TestOutputMode::eXUnit2;
}
else if( strcmp(arg, "-category") == 0 )
{
@@ -637,6 +286,10 @@ Result parseOptions(int* argc, char** argv)
return res;
}
}
+ else if (strcmp(arg, "-unitTests") == 0)
+ {
+ g_options.unitTests = true;
+ }
else
{
fprintf(stderr, "unknown option '%s'\n", arg);
@@ -831,7 +484,7 @@ TestResult gatherTestOptions(
if(!category)
{
- return kTestResult_Fail;
+ return TestResult::eFail;
}
testOptions.categories.Add(category);
@@ -846,7 +499,7 @@ TestResult gatherTestOptions(
break;
case 0: case '\r': case '\n':
- return kTestResult_Fail;
+ return TestResult::eFail;
}
break;
@@ -863,7 +516,7 @@ TestResult gatherTestOptions(
cursor++;
else
{
- return kTestResult_Fail;
+ return TestResult::eFail;
}
// Next scan for a sub-command name
@@ -880,7 +533,7 @@ TestResult gatherTestOptions(
break;
case 0: case '\r': case '\n':
- return kTestResult_Fail;
+ return TestResult::eFail;
}
break;
@@ -893,7 +546,7 @@ TestResult gatherTestOptions(
cursor++;
else
{
- return kTestResult_Fail;
+ return TestResult::eFail;
}
// Now scan for arguments. For now we just assume that
@@ -909,7 +562,7 @@ TestResult gatherTestOptions(
case 0: case '\r': case '\n':
skipToEndOfLine(&cursor);
testList->tests.Add(testOptions);
- return kTestResult_Pass;
+ return TestResult::ePass;
default:
break;
@@ -950,7 +603,7 @@ TestResult gatherTestsForFile(
}
catch (Slang::IOException)
{
- return kTestResult_Fail;
+ return TestResult::eFail;
}
@@ -966,12 +619,12 @@ TestResult gatherTestsForFile(
// Look for a pattern that matches what we want
if(match(&cursor, "//TEST_IGNORE_FILE"))
{
- return kTestResult_Ignored;
+ return TestResult::eIgnored;
}
else if(match(&cursor, "//TEST"))
{
- if(gatherTestOptions(&cursor, testList) != kTestResult_Pass)
- return kTestResult_Fail;
+ if(gatherTestOptions(&cursor, testList) != TestResult::ePass)
+ return TestResult::eFail;
}
else
{
@@ -979,7 +632,7 @@ TestResult gatherTestsForFile(
}
}
- return kTestResult_Pass;
+ return TestResult::ePass;
}
OSError spawnAndWait(TestContext* context, const String& testPath, OSProcessSpawner& spawner)
@@ -989,14 +642,14 @@ OSError spawnAndWait(TestContext* context, const String& testPath, OSProcessSpaw
if(context->m_isVerbose)
{
String commandLine = spawner.getCommandLine();
- context->messageFormat(MessageType::INFO, "%s\n", commandLine.begin());
+ context->messageFormat(TestMessageType::eInfo, "%s\n", commandLine.begin());
}
OSError err = spawner.spawnAndWaitForCompletion();
if (err != kOSError_None)
{
// fprintf(stderr, "failed to run test '%S'\n", testPath.ToWString());
- context->messageFormat(MessageType::RUN_ERROR, "failed to run test '%S'", testPath.ToWString().begin());
+ context->messageFormat(TestMessageType::eRunError, "failed to run test '%S'", testPath.ToWString().begin());
}
return err;
}
@@ -1078,7 +731,7 @@ TestResult runSimpleTest(TestContext* context, TestInput& input)
if (spawnAndWait(context, outputStem, spawner) != kOSError_None)
{
- return kTestResult_Fail;
+ return TestResult::eFail;
}
String actualOutput = getOutput(spawner);
@@ -1100,19 +753,19 @@ TestResult runSimpleTest(TestContext* context, TestInput& input)
expectedOutput = "result code = 0\nstandard error = {\n}\nstandard output = {\n}\n";
}
- TestResult result = kTestResult_Pass;
+ TestResult result = TestResult::ePass;
// Otherwise we compare to the expected output
if (actualOutput != expectedOutput)
{
context->dumpOutputDifference(expectedOutput, actualOutput);
- result = kTestResult_Fail;
+ result = TestResult::eFail;
}
// If the test failed, then we write the actual output to a file
// so that we can easily diff it from the command line and
// diagnose the problem.
- if (result == kTestResult_Fail)
+ if (result == TestResult::eFail)
{
String actualOutputPath = outputStem + ".actual";
Slang::File::WriteAllText(actualOutputPath, actualOutput);
@@ -1140,7 +793,7 @@ TestResult runReflectionTest(TestContext* context, TestInput& input)
if (spawnAndWait(context, outputStem, spawner) != kOSError_None)
{
- return kTestResult_Fail;
+ return TestResult::eFail;
}
String actualOutput = getOutput(spawner);
@@ -1162,18 +815,18 @@ TestResult runReflectionTest(TestContext* context, TestInput& input)
expectedOutput = "result code = 0\nstandard error = {\n}\nstandard output = {\n}\n";
}
- TestResult result = kTestResult_Pass;
+ TestResult result = TestResult::ePass;
// Otherwise we compare to the expected output
if (actualOutput != expectedOutput)
{
- result = kTestResult_Fail;
+ result = TestResult::eFail;
}
// If the test failed, then we write the actual output to a file
// so that we can easily diff it from the command line and
// diagnose the problem.
- if (result == kTestResult_Fail)
+ if (result == TestResult::eFail)
{
String actualOutputPath = outputStem + ".actual";
Slang::File::WriteAllText(actualOutputPath, actualOutput);
@@ -1225,24 +878,24 @@ TestResult runEvalTest(TestContext* context, TestInput& input)
if (spawnAndWait(context, outputStem, spawner) != kOSError_None)
{
- return kTestResult_Fail;
+ return TestResult::eFail;
}
String actualOutput = getOutput(spawner);
String expectedOutput = getExpectedOutput(outputStem);
- TestResult result = kTestResult_Pass;
+ TestResult result = TestResult::ePass;
// Otherwise we compare to the expected output
if (actualOutput != expectedOutput)
{
- result = kTestResult_Fail;
+ result = TestResult::eFail;
}
// If the test failed, then we write the actual output to a file
// so that we can easily diff it from the command line and
// diagnose the problem.
- if (result == kTestResult_Fail)
+ if (result == TestResult::eFail)
{
String actualOutputPath = outputStem + ".actual";
Slang::File::WriteAllText(actualOutputPath, actualOutput);
@@ -1281,7 +934,7 @@ TestResult runCrossCompilerTest(TestContext* context, TestInput& input)
if (spawnAndWait(context, outputStem, expectedSpawner) != kOSError_None)
{
- return kTestResult_Fail;
+ return TestResult::eFail;
}
String expectedOutput = getOutput(expectedSpawner);
@@ -1292,27 +945,27 @@ TestResult runCrossCompilerTest(TestContext* context, TestInput& input)
}
catch (Slang::IOException)
{
- return kTestResult_Fail;
+ return TestResult::eFail;
}
if (spawnAndWait(context, outputStem, actualSpawner) != kOSError_None)
{
- return kTestResult_Fail;
+ return TestResult::eFail;
}
String actualOutput = getOutput(actualSpawner);
- TestResult result = kTestResult_Pass;
+ TestResult result = TestResult::ePass;
// Otherwise we compare to the expected output
if (actualOutput != expectedOutput)
{
- result = kTestResult_Fail;
+ result = TestResult::eFail;
}
// If the test failed, then we write the actual output to a file
// so that we can easily diff it from the command line and
// diagnose the problem.
- if (result == kTestResult_Fail)
+ if (result == TestResult::eFail)
{
String actualOutputPath = outputStem + ".actual";
Slang::File::WriteAllText(actualOutputPath, actualOutput);
@@ -1346,7 +999,7 @@ TestResult generateHLSLBaseline(TestContext* context, TestInput& input)
if (spawnAndWait(context, outputStem, spawner) != kOSError_None)
{
- return kTestResult_Fail;
+ return TestResult::eFail;
}
String expectedOutput = getOutput(spawner);
@@ -1357,9 +1010,9 @@ TestResult generateHLSLBaseline(TestContext* context, TestInput& input)
}
catch (Slang::IOException)
{
- return kTestResult_Fail;
+ return TestResult::eFail;
}
- return kTestResult_Pass;
+ return TestResult::ePass;
}
TestResult runHLSLComparisonTest(TestContext* context, TestInput& input)
@@ -1394,7 +1047,7 @@ TestResult runHLSLComparisonTest(TestContext* context, TestInput& input)
if (spawnAndWait(context, outputStem, spawner) != kOSError_None)
{
- return kTestResult_Fail;
+ return TestResult::eFail;
}
// We ignore output to stdout, and only worry about what the compiler
@@ -1426,26 +1079,26 @@ TestResult runHLSLComparisonTest(TestContext* context, TestInput& input)
{
}
- TestResult result = kTestResult_Pass;
+ TestResult result = TestResult::ePass;
// If no expected output file was found, then we
// expect everything to be empty
if (expectedOutput.Length() == 0)
{
- if (resultCode != 0) result = kTestResult_Fail;
- if (standardError.Length() != 0) result = kTestResult_Fail;
- if (standardOutput.Length() != 0) result = kTestResult_Fail;
+ if (resultCode != 0) result = TestResult::eFail;
+ if (standardError.Length() != 0) result = TestResult::eFail;
+ if (standardOutput.Length() != 0) result = TestResult::eFail;
}
// Otherwise we compare to the expected output
else if (actualOutput != expectedOutput)
{
- result = kTestResult_Fail;
+ result = TestResult::eFail;
}
// If the test failed, then we write the actual output to a file
// so that we can easily diff it from the command line and
// diagnose the problem.
- if (result == kTestResult_Fail)
+ if (result == TestResult::eFail)
{
String actualOutputPath = outputStem + ".actual";
Slang::File::WriteAllText(actualOutputPath, actualOutput);
@@ -1494,7 +1147,7 @@ TestResult doGLSLComparisonTestRun(TestContext* context,
if (spawnAndWait(context, outputStem, spawner) != kOSError_None)
{
- return kTestResult_Fail;
+ return TestResult::eFail;
}
OSProcessSpawner::ResultCode resultCode = spawner.getResultCode();
@@ -1517,7 +1170,7 @@ TestResult doGLSLComparisonTestRun(TestContext* context,
*outOutput = output;
- return kTestResult_Pass;
+ return TestResult::ePass;
}
TestResult runGLSLComparisonTest(TestContext* context, TestInput& input)
@@ -1534,17 +1187,17 @@ TestResult runGLSLComparisonTest(TestContext* context, TestInput& input)
Slang::File::WriteAllText(outputStem + ".expected", expectedOutput);
Slang::File::WriteAllText(outputStem + ".actual", actualOutput);
- if( hlslResult == kTestResult_Fail ) return kTestResult_Fail;
- if( slangResult == kTestResult_Fail ) return kTestResult_Fail;
+ if( hlslResult == TestResult::eFail ) return TestResult::eFail;
+ if( slangResult == TestResult::eFail ) return TestResult::eFail;
if (actualOutput != expectedOutput)
{
context->dumpOutputDifference(expectedOutput, actualOutput);
- return kTestResult_Fail;
+ return TestResult::eFail;
}
- return kTestResult_Pass;
+ return TestResult::ePass;
}
@@ -1557,7 +1210,7 @@ TestResult runComputeComparisonImpl(TestContext* context, TestInput& input, cons
const String referenceOutput = findExpectedPath(input, ".expected.txt");
if (referenceOutput.Length() <= 0)
{
- return kTestResult_Fail;
+ return TestResult::eFail;
}
OSProcessSpawner spawner;
@@ -1581,7 +1234,7 @@ TestResult runComputeComparisonImpl(TestContext* context, TestInput& input, cons
if (spawnAndWait(context, outputStem, spawner) != kOSError_None)
{
printf("error spawning render-test\n");
- return kTestResult_Fail;
+ return TestResult::eFail;
}
auto actualOutput = getOutput(spawner);
@@ -1593,7 +1246,7 @@ TestResult runComputeComparisonImpl(TestContext* context, TestInput& input, cons
String actualOutputPath = outputStem + ".actual";
Slang::File::WriteAllText(actualOutputPath, actualOutput);
- return kTestResult_Fail;
+ return TestResult::eFail;
}
// check against reference output
@@ -1601,24 +1254,24 @@ TestResult runComputeComparisonImpl(TestContext* context, TestInput& input, cons
{
printf("render-test not producing expected outputs.\n");
printf("render-test output:\n%s\n", actualOutput.Buffer());
- return kTestResult_Fail;
+ return TestResult::eFail;
}
if (!File::Exists(referenceOutput))
{
printf("referenceOutput %s not found.\n", referenceOutput.Buffer());
- return kTestResult_Fail;
+ return TestResult::eFail;
}
auto actualOutputContent = File::ReadAllText(actualOutputFile);
auto actualProgramOutput = Split(actualOutputContent, '\n');
auto referenceProgramOutput = Split(File::ReadAllText(referenceOutput), '\n');
auto printOutput = [&]()
{
- context->messageFormat(MessageType::TEST_FAILURE, "output mismatch! actual output: {\n%s\n}, \n%s\n", actualOutputContent.Buffer(), actualOutput.Buffer());
+ context->messageFormat(TestMessageType::eTestFailure, "output mismatch! actual output: {\n%s\n}, \n%s\n", actualOutputContent.Buffer(), actualOutput.Buffer());
};
if (actualProgramOutput.Count() < referenceProgramOutput.Count())
{
printOutput();
- return kTestResult_Fail;
+ return TestResult::eFail;
}
for (int i = 0; i < (int)referenceProgramOutput.Count(); i++)
{
@@ -1632,13 +1285,13 @@ TestResult runComputeComparisonImpl(TestContext* context, TestInput& input, cons
if (actual != uval)
{
printOutput();
- return kTestResult_Fail;
+ return TestResult::eFail;
}
else
- return kTestResult_Pass;
+ return TestResult::ePass;
}
}
- return kTestResult_Pass;
+ return TestResult::ePass;
}
TestResult runSlangComputeComparisonTest(TestContext* context, TestInput& input)
@@ -1684,7 +1337,7 @@ TestResult doRenderComparisonTestRun(TestContext* context, TestInput& input, cha
if (spawnAndWait(context, outputStem, spawner) != kOSError_None)
{
- return kTestResult_Fail;
+ return TestResult::eFail;
}
OSProcessSpawner::ResultCode resultCode = spawner.getResultCode();
@@ -1707,7 +1360,7 @@ TestResult doRenderComparisonTestRun(TestContext* context, TestInput& input, cha
*outOutput = output;
- return kTestResult_Pass;
+ return TestResult::ePass;
}
TestResult doImageComparison(TestContext* context, String const& filePath)
@@ -1729,19 +1382,19 @@ TestResult doImageComparison(TestContext* context, String const& filePath)
if(!expectedData)
{
- context->messageFormat(MessageType::RUN_ERROR, "Unable to load image ;%s'", expectedPath.Buffer());
- return kTestResult_Fail;
+ context->messageFormat(TestMessageType::eRunError, "Unable to load image ;%s'", expectedPath.Buffer());
+ return TestResult::eFail;
}
if(!actualData)
{
- context->messageFormat(MessageType::RUN_ERROR, "Unable to load image '%s'", actualPath.Buffer());
- return kTestResult_Fail;
+ context->messageFormat(TestMessageType::eRunError, "Unable to load image '%s'", actualPath.Buffer());
+ return TestResult::eFail;
}
if(expectedX != actualX || expectedY != actualY || expectedN != actualN)
{
- context->messageFormat(MessageType::TEST_FAILURE, "Images are different sizes '%s' '%s'", actualPath.Buffer(), expectedPath.Buffer());
- return kTestResult_Fail;
+ context->messageFormat(TestMessageType::eTestFailure, "Images are different sizes '%s' '%s'", actualPath.Buffer(), expectedPath.Buffer());
+ return TestResult::eFail;
}
unsigned char* expectedCursor = expectedData;
@@ -1781,7 +1434,7 @@ TestResult doImageComparison(TestContext* context, String const& filePath)
// cases where vertex shader results lead to rendering that is off
// by one pixel...
- context->messageFormat(MessageType::TEST_FAILURE, "image compare failure at (%d,%d) channel %d. expected %d got %d (absolute error: %d, relative error: %f)\n",
+ context->messageFormat(TestMessageType::eTestFailure, "image compare failure at (%d,%d) channel %d. expected %d got %d (absolute error: %d, relative error: %f)\n",
x, y, n,
expectedVal,
actualVal,
@@ -1789,12 +1442,12 @@ TestResult doImageComparison(TestContext* context, String const& filePath)
relativeDiff);
// There was a difference we couldn't excuse!
- return kTestResult_Fail;
+ return TestResult::eFail;
}
}
}
- return kTestResult_Pass;
+ return TestResult::ePass;
}
TestResult runHLSLRenderComparisonTestImpl(
@@ -1815,23 +1468,23 @@ TestResult runHLSLRenderComparisonTestImpl(
Slang::File::WriteAllText(outputStem + ".expected", expectedOutput);
Slang::File::WriteAllText(outputStem + ".actual", actualOutput);
- if( hlslResult == kTestResult_Fail ) return kTestResult_Fail;
- if( slangResult == kTestResult_Fail ) return kTestResult_Fail;
+ if( hlslResult == TestResult::eFail ) return TestResult::eFail;
+ if( slangResult == TestResult::eFail ) return TestResult::eFail;
if (actualOutput != expectedOutput)
{
context->dumpOutputDifference(expectedOutput, actualOutput);
- return kTestResult_Fail;
+ return TestResult::eFail;
}
// Next do an image comparison on the expected output images!
TestResult imageCompareResult = doImageComparison(context, outputStem);
- if(imageCompareResult != kTestResult_Pass)
+ if(imageCompareResult != TestResult::ePass)
return imageCompareResult;
- return kTestResult_Pass;
+ return TestResult::ePass;
}
TestResult runHLSLRenderComparisonTest(TestContext* context, TestInput& input)
@@ -1851,7 +1504,7 @@ TestResult runHLSLAndGLSLRenderComparisonTest(TestContext* context, TestInput& i
TestResult skipTest(TestContext* /* context */, TestInput& /*input*/)
{
- return kTestResult_Ignored;
+ return TestResult::eIgnored;
}
@@ -1957,7 +1610,7 @@ TestResult runTest(
// If this test can be ignored
if (canIgnoreTestWithDisabledRenderer(testOptions))
{
- return kTestResult_Ignored;
+ return TestResult::eIgnored;
}
// based on command name, dispatch to an appropriate callback
@@ -2008,13 +1661,19 @@ TestResult runTest(
testInput.testOptions = &testOptions;
testInput.testList = &testList;
- context->startTest(outputStem);
- return context->endTest(ii->callback(context, testInput));
+ {
+ TestContext::Scope scope(context, outputStem);
+
+ TestResult testResult = ii->callback(context, testInput);
+ context->addResult(testResult);
+
+ return testResult;
+ }
}
// No actual test runner found!
- return kTestResult_Fail;
+ return TestResult::eFail;
}
bool testCategoryMatches(
@@ -2073,7 +1732,7 @@ void runTestsOnFile(
// Gather a list of tests to run
FileTestList testList;
- if( gatherTestsForFile(filePath, &testList) == kTestResult_Ignored )
+ if( gatherTestsForFile(filePath, &testList) == TestResult::eIgnored )
{
// Test was explicitly ignored
return;
@@ -2082,7 +1741,7 @@ void runTestsOnFile(
// Note cases where a test file exists, but we found nothing to run
if( testList.tests.Count() == 0 )
{
- context->addResult(filePath, kTestResult_Ignored);
+ context->addTest(filePath, TestResult::eIgnored);
return;
}
@@ -2233,62 +1892,6 @@ void runTestsInDirectory(
}
}
-static void appendXmlEncode(char c, StringBuilder& out)
-{
- switch (c)
- {
- case '&': out << "&amp;"; break;
- case '<': out << "&lt;"; break;
- case '>': out << "&gt;"; break;
- case '\'': out << "&apos;"; break;
- case '"': out << "&quot;"; break;
- default: out.Append(c);
- }
-}
-
-static bool isXmlEncodeChar(char c)
-{
- switch (c)
- {
- case '&':
- case '<':
- case '>':
- {
- return true;
- }
- }
- return false;
-}
-
-static void appendXmlEncode(const String& in, StringBuilder& out)
-{
- const char* cur = in.Buffer();
- const char* end = cur + in.Length();
-
- while (cur < end)
- {
- const char* start = cur;
- // Look for a run of non encoded
- while (cur < end && !isXmlEncodeChar(*cur))
- {
- cur++;
- }
- // Write it
- if (cur > start)
- {
- out.Append(start, UInt(end - start));
- }
-
- // if not at the end, we must be on an xml encoded character, so just output it xml encoded.
- if (cur < end)
- {
- const char encodeChar = *cur++;
- assert(isXmlEncodeChar(encodeChar));
- appendXmlEncode(encodeChar, out);
- }
- }
-}
-
//
@@ -2330,7 +1933,7 @@ int main(
// Exclude rendering tests when building under AppVeyor.
//
// TODO: this is very ad hoc, and we should do something cleaner.
- if( g_options.outputMode == kOutputMode_AppVeyor )
+ if( g_options.outputMode == TestOutputMode::eAppVeyor )
{
g_options.excludeCategories.Add(renderTestCategory, renderTestCategory);
g_options.excludeCategories.Add(vulkanTestCategory, vulkanTestCategory);
@@ -2342,104 +1945,37 @@ int main(
context.m_dumpOutputOnFailure = g_options.dumpOutputOnFailure;
context.m_isVerbose = g_options.shouldBeVerbose;
- // Enumerate test files according to policy
- // TODO: add more directories to this list
- // TODO: allow for a command-line argument to select a particular directory
- runTestsInDirectory(&context, "tests/");
-
- auto passCount = context.m_passedTestCount;
- auto rawTotal = context.m_totalTestCount;
- auto ignoredCount = context.m_ignoredTestCount;
-
- auto runTotal = rawTotal - ignoredCount;
-
- switch (g_options.outputMode)
+ if (g_options.unitTests)
{
- default:
- {
- if (!context.m_totalTestCount)
- {
- printf("no tests run\n");
- return 0;
- }
-
- printf("\n===\n%d%% of tests passed (%d/%d)", (passCount*100) / runTotal, passCount, runTotal);
- if(ignoredCount)
- {
- printf(", %d tests ignored", ignoredCount);
- }
- printf("\n===\n\n");
+ TestContext::set(&context);
- if(context.m_failedTestCount)
- {
- printf("failing tests:\n");
- printf("---\n");
- for(const auto& testInfo : context.m_testInfos)
- {
- if (testInfo.testResult == kTestResult_Fail)
- {
- printf("%s\n", testInfo.name.Buffer());
- }
- }
- printf("---\n");
- }
- break;
- }
- case kOutputMode_xUnit:
+ // Run the unit tests
+ TestRegister* cur = TestRegister::s_first;
+ while (cur)
{
- // xUnit 1.0 format
-
- printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
- printf("<testsuites tests=\"%d\" failures=\"%d\" disabled=\"%d\" errors=\"0\" name=\"AllTests\">\n", context.m_totalTestCount, context.m_failedTestCount, context.m_ignoredTestCount);
- printf(" <testsuite name=\"all\" tests=\"%d\" failures=\"%d\" disabled=\"%d\" errors=\"0\" time=\"0\">\n", context.m_totalTestCount, context.m_failedTestCount, context.m_ignoredTestCount);
+ context.startTest(cur->m_name);
- for (const auto& testInfo : context.m_testInfos)
- {
- const int numFailed = (testInfo.testResult == kTestResult_Fail);
- const int numIgnored = (testInfo.testResult == kTestResult_Ignored);
- //int numPassed = (testInfo.testResult == kTestResult_Pass);
+ // Run the test function
+ cur->m_func();
- if (testInfo.testResult == kTestResult_Pass)
- {
- printf(" <testcase name=\"%s\" status=\"run\"/>\n", testInfo.name.Buffer());
- }
- else
- {
- printf(" <testcase name=\"%s\" status=\"run\">\n", testInfo.name.Buffer());
- switch (testInfo.testResult)
- {
- case kTestResult_Fail:
- {
- StringBuilder buf;
- appendXmlEncode(testInfo.message, buf);
-
- printf(" <error>\n");
- printf("%s", buf.Buffer());
- printf(" </error>\n");
- break;
- }
- case kTestResult_Ignored:
- {
- printf(" <skip>Ignored</skip>\n");
- break;
- }
- default: break;
- }
- printf(" </testcase>\n");
- }
- }
+ context.endTest();
- printf(" </testsuite>\n");
- printf("</testSuites>\n");
- break;
- }
- case kOutputMode_xUnit2:
- {
- // https://xunit.github.io/docs/format-xml-v2
- assert("Not currently supported");
- break;
+ // Next
+ cur = cur->m_next;
}
+
+ TestContext::set(nullptr);
}
+ else
+ {
+ // Enumerate test files according to policy
+ // TODO: add more directories to this list
+ // TODO: allow for a command-line argument to select a particular directory
+ runTestsInDirectory(&context, "tests/");
+
+ }
+
+ context.outputSummary();
- return passCount == runTotal ? 0 : 1;
+ return context.didAllSucceed() ? 0 : 1;
}
diff --git a/tools/slang-test/slang-test.vcxproj b/tools/slang-test/slang-test.vcxproj
index 9524529bb..b94f2f627 100644
--- a/tools/slang-test/slang-test.vcxproj
+++ b/tools/slang-test/slang-test.vcxproj
@@ -164,11 +164,15 @@
<ItemGroup>
<ClInclude Include="os.h" />
<ClInclude Include="render-api-util.h" />
+ <ClInclude Include="test-context.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="main.cpp" />
<ClCompile Include="os.cpp" />
<ClCompile Include="render-api-util.cpp" />
+ <ClCompile Include="test-context.cpp" />
+ <ClCompile Include="unit-test-free-list.cpp" />
+ <ClCompile Include="unit-test-memory-arena.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\source\core\core.vcxproj">
diff --git a/tools/slang-test/slang-test.vcxproj.filters b/tools/slang-test/slang-test.vcxproj.filters
index f22903aa6..3d80416f8 100644
--- a/tools/slang-test/slang-test.vcxproj.filters
+++ b/tools/slang-test/slang-test.vcxproj.filters
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Header Files">
@@ -15,6 +15,9 @@
<ClInclude Include="render-api-util.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="test-context.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="main.cpp">
@@ -26,5 +29,14 @@
<ClCompile Include="render-api-util.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="test-context.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="unit-test-free-list.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="unit-test-memory-arena.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
</ItemGroup>
</Project> \ No newline at end of file
diff --git a/tools/slang-test/test-context.cpp b/tools/slang-test/test-context.cpp
new file mode 100644
index 000000000..f77262001
--- /dev/null
+++ b/tools/slang-test/test-context.cpp
@@ -0,0 +1,450 @@
+// test-context.cpp
+#include "test-context.h"
+
+#include "os.h"
+#include "../../source/core/slang-string-util.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+using namespace Slang;
+
+/* static */TestContext* TestContext::s_context = nullptr;
+/* static */TestRegister* TestRegister::s_first;
+
+static void appendXmlEncode(char c, StringBuilder& out)
+{
+ switch (c)
+ {
+ case '&': out << "&amp;"; break;
+ case '<': out << "&lt;"; break;
+ case '>': out << "&gt;"; break;
+ case '\'': out << "&apos;"; break;
+ case '"': out << "&quot;"; break;
+ default: out.Append(c);
+ }
+}
+
+static bool isXmlEncodeChar(char c)
+{
+ switch (c)
+ {
+ case '&':
+ case '<':
+ case '>':
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+static void appendXmlEncode(const String& in, StringBuilder& out)
+{
+ const char* cur = in.Buffer();
+ const char* end = cur + in.Length();
+
+ while (cur < end)
+ {
+ const char* start = cur;
+ // Look for a run of non encoded
+ while (cur < end && !isXmlEncodeChar(*cur))
+ {
+ cur++;
+ }
+ // Write it
+ if (cur > start)
+ {
+ out.Append(start, UInt(end - start));
+ }
+
+ // if not at the end, we must be on an xml encoded character, so just output it xml encoded.
+ if (cur < end)
+ {
+ const char encodeChar = *cur++;
+ assert(isXmlEncodeChar(encodeChar));
+ appendXmlEncode(encodeChar, out);
+ }
+ }
+}
+
+TestContext::TestContext(TestOutputMode outputMode) :
+ m_outputMode(outputMode)
+{
+ m_totalTestCount = 0;
+ m_passedTestCount = 0;
+ m_failedTestCount = 0;
+ m_ignoredTestCount = 0;
+
+ m_maxTestResults = 10;
+
+ m_inTest = false;
+ m_dumpOutputOnFailure = false;
+ m_isVerbose = false;
+}
+
+bool TestContext::canWriteStdError() const
+{
+ switch (m_outputMode)
+ {
+ case TestOutputMode::eXUnit:
+ case TestOutputMode::eXUnit2:
+ {
+ return false;
+ }
+ default: return true;
+ }
+}
+
+void TestContext::startTest(const String& testName)
+{
+ assert(!m_inTest);
+ m_inTest = true;
+
+ m_numCurrentResults = 0;
+ m_currentInfo = TestInfo();
+ m_currentInfo.name = testName;
+ m_currentMessage.Clear();
+}
+
+void TestContext::endTest()
+{
+ assert(m_inTest);
+
+ m_currentInfo.message = m_currentMessage;
+
+ _addResult(m_currentInfo);
+
+ m_inTest = false;
+}
+
+void TestContext::addResult(TestResult result)
+{
+ assert(m_inTest);
+
+ m_currentInfo.testResult = combine(m_currentInfo.testResult, result);
+ m_numCurrentResults++;
+}
+
+void TestContext::addResultWithLocation(TestResult result, const char* testText, const char* file, int line)
+{
+ assert(m_inTest);
+ m_numCurrentResults++;
+
+ m_currentInfo.testResult = combine(m_currentInfo.testResult, result);
+ if (result != TestResult::eFail)
+ {
+ // We don't need to output the result if it
+ return;
+ }
+
+ if (m_maxTestResults > 0)
+ {
+ if (m_numCurrentResults > m_maxTestResults)
+ {
+ if (m_numCurrentResults == m_maxTestResults + 1)
+ {
+ message(TestMessageType::eInfo, "...");
+ }
+ return;
+ }
+ }
+
+ StringBuilder buf;
+ buf << testText << " - " << file << " (" << line << ")";
+
+ message(TestMessageType::eTestFailure, buf);
+}
+
+void TestContext::addResultWithLocation(bool testSucceeded, const char* testText, const char* file, int line)
+{
+ addResultWithLocation(testSucceeded ? TestResult::ePass : TestResult::eFail, testText, file, line);
+}
+
+TestResult TestContext::addTest(const String& testName, bool isPass)
+{
+ const TestResult res = isPass ? TestResult::ePass : TestResult::eFail;
+ addTest(testName, res);
+ return res;
+}
+
+void TestContext::dumpOutputDifference(const String& expectedOutput, const String& actualOutput)
+{
+ StringBuilder builder;
+
+ StringUtil::appendFormat(builder,
+ "ERROR:\n"
+ "EXPECTED{{{\n%s}}}\n"
+ "ACTUAL{{{\n%s}}}\n",
+ expectedOutput.Buffer(),
+ actualOutput.Buffer());
+
+
+ if (m_dumpOutputOnFailure && canWriteStdError())
+ {
+ fprintf(stderr, "%s", builder.Buffer());
+ fflush(stderr);
+ }
+
+ // Add to the m_currentInfo
+ message(TestMessageType::eTestFailure, builder);
+}
+
+void TestContext::_addResult(const TestInfo& info)
+{
+ m_totalTestCount++;
+
+ switch (info.testResult)
+ {
+ case TestResult::eFail:
+ m_failedTestCount++;
+ break;
+
+ case TestResult::ePass:
+ m_passedTestCount++;
+ break;
+
+ case TestResult::eIgnored:
+ m_ignoredTestCount++;
+ break;
+
+ default:
+ assert(!"unexpected");
+ break;
+ }
+
+ m_testInfos.Add(info);
+
+ // printf("OUTPUT_MODE: %d\n", options.outputMode);
+ switch (m_outputMode)
+ {
+ default:
+ {
+ char const* resultString = "UNEXPECTED";
+ switch (info.testResult)
+ {
+ case TestResult::eFail: resultString = "FAILED"; break;
+ case TestResult::ePass: resultString = "passed"; break;
+ case TestResult::eIgnored: resultString = "ignored"; break;
+ default:
+ assert(!"unexpected");
+ break;
+ }
+ printf("%s test: '%S'\n", resultString, info.name.ToWString().begin());
+ break;
+ }
+ case TestOutputMode::eXUnit2:
+ case TestOutputMode::eXUnit:
+ {
+ // Don't output anything -> we'll output all in one go at the end
+ break;
+ }
+ case TestOutputMode::eAppVeyor:
+ {
+ char const* resultString = "None";
+ switch (info.testResult)
+ {
+ case TestResult::eFail: resultString = "Failed"; break;
+ case TestResult::ePass: resultString = "Passed"; break;
+ case TestResult::eIgnored: resultString = "Ignored"; break;
+ default:
+ assert(!"unexpected");
+ break;
+ }
+
+ OSProcessSpawner spawner;
+ spawner.pushExecutableName("appveyor");
+ spawner.pushArgument("AddTest");
+ spawner.pushArgument(info.name);
+ spawner.pushArgument("-FileName");
+ // TODO: this isn't actually a file name in all cases
+ spawner.pushArgument(info.name);
+ spawner.pushArgument("-Framework");
+ spawner.pushArgument("slang-test");
+ spawner.pushArgument("-Outcome");
+ spawner.pushArgument(resultString);
+
+ auto err = spawner.spawnAndWaitForCompletion();
+
+ if (err != kOSError_None)
+ {
+ messageFormat(TestMessageType::eInfo, "failed to add appveyor test results for '%S'\n", info.name.ToWString().begin());
+
+#if 0
+ fprintf(stderr, "[%d] TEST RESULT: %s {%d} {%s} {%s}\n", err, spawner.commandLine_.Buffer(),
+ spawner.getResultCode(),
+ spawner.getStandardOutput().begin(),
+ spawner.getStandardError().begin());
+#endif
+ }
+
+ break;
+ }
+ }
+}
+
+void TestContext::addTest(const String& testName, TestResult testResult)
+{
+ // Can't add this way if in test
+ assert(!m_inTest);
+
+ TestInfo info;
+ info.name = testName;
+ info.testResult = testResult;
+ _addResult(info);
+}
+
+void TestContext::message(TestMessageType type, const String& message)
+{
+ if (type == TestMessageType::eInfo)
+ {
+ if (m_isVerbose && canWriteStdError())
+ {
+ fputs(message.Buffer(), stderr);
+ }
+
+ // Just dump out if can dump out
+ return;
+ }
+
+ if (canWriteStdError())
+ {
+ if (type == TestMessageType::eRunError || type == TestMessageType::eTestFailure)
+ {
+ fprintf(stderr, "error: ");
+ fputs(message.Buffer(), stderr);
+ fprintf(stderr, "\n");
+ }
+ else
+ {
+ fputs(message.Buffer(), stderr);
+ }
+ }
+
+ if (m_currentMessage.Length() > 0)
+ {
+ m_currentMessage << "\n";
+ }
+ m_currentMessage.Append(message);
+}
+
+void TestContext::messageFormat(TestMessageType type, char const* format, ...)
+{
+ StringBuilder builder;
+
+ va_list args;
+ va_start(args, format);
+ StringUtil::append(format, args, builder);
+ va_end(args);
+
+ message(type, builder);
+}
+
+bool TestContext::didAllSucceed() const
+{
+ return m_passedTestCount == (m_totalTestCount - m_ignoredTestCount);
+}
+
+void TestContext::outputSummary()
+{
+ auto passCount = m_passedTestCount;
+ auto rawTotal = m_totalTestCount;
+ auto ignoredCount = m_ignoredTestCount;
+
+ auto runTotal = rawTotal - ignoredCount;
+
+ switch (m_outputMode)
+ {
+ default:
+ {
+ if (!m_totalTestCount)
+ {
+ printf("no tests run\n");
+ return;
+ }
+
+ int percentPassed = 0;
+ if (runTotal > 0)
+ {
+ percentPassed = (passCount * 100) / runTotal;
+ }
+
+ printf("\n===\n%d%% of tests passed (%d/%d)", percentPassed, passCount, runTotal);
+ if (ignoredCount)
+ {
+ printf(", %d tests ignored", ignoredCount);
+ }
+ printf("\n===\n\n");
+
+ if (m_failedTestCount)
+ {
+ printf("failing tests:\n");
+ printf("---\n");
+ for (const auto& testInfo : m_testInfos)
+ {
+ if (testInfo.testResult == TestResult::eFail)
+ {
+ printf("%s\n", testInfo.name.Buffer());
+ }
+ }
+ printf("---\n");
+ }
+ break;
+ }
+ case TestOutputMode::eXUnit:
+ {
+ // xUnit 1.0 format
+
+ printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+ printf("<testsuites tests=\"%d\" failures=\"%d\" disabled=\"%d\" errors=\"0\" name=\"AllTests\">\n", m_totalTestCount, m_failedTestCount, m_ignoredTestCount);
+ printf(" <testsuite name=\"all\" tests=\"%d\" failures=\"%d\" disabled=\"%d\" errors=\"0\" time=\"0\">\n", m_totalTestCount, m_failedTestCount, m_ignoredTestCount);
+
+ for (const auto& testInfo : m_testInfos)
+ {
+ const int numFailed = (testInfo.testResult == TestResult::eFail);
+ const int numIgnored = (testInfo.testResult == TestResult::eIgnored);
+ //int numPassed = (testInfo.testResult == TestResult::ePass);
+
+ if (testInfo.testResult == TestResult::ePass)
+ {
+ printf(" <testcase name=\"%s\" status=\"run\"/>\n", testInfo.name.Buffer());
+ }
+ else
+ {
+ printf(" <testcase name=\"%s\" status=\"run\">\n", testInfo.name.Buffer());
+ switch (testInfo.testResult)
+ {
+ case TestResult::eFail:
+ {
+ StringBuilder buf;
+ appendXmlEncode(testInfo.message, buf);
+
+ printf(" <error>\n");
+ printf("%s", buf.Buffer());
+ printf(" </error>\n");
+ break;
+ }
+ case TestResult::eIgnored:
+ {
+ printf(" <skip>Ignored</skip>\n");
+ break;
+ }
+ default: break;
+ }
+ printf(" </testcase>\n");
+ }
+ }
+
+ printf(" </testsuite>\n");
+ printf("</testSuites>\n");
+ break;
+ }
+ case TestOutputMode::eXUnit2:
+ {
+ // https://xunit.github.io/docs/format-xml-v2
+ assert("Not currently supported");
+ break;
+ }
+ }
+} \ No newline at end of file
diff --git a/tools/slang-test/test-context.h b/tools/slang-test/test-context.h
new file mode 100644
index 000000000..22042a4e6
--- /dev/null
+++ b/tools/slang-test/test-context.h
@@ -0,0 +1,139 @@
+// test-context.h
+
+#include "../../source/core/slang-string-util.h"
+
+#define SLANG_CHECK(x) TestContext::get()->addResultWithLocation((x), #x, __FILE__, __LINE__);
+
+struct TestRegister
+{
+ typedef void (*TestFunc)();
+
+ TestRegister(const char* name, TestFunc func):
+ m_next(s_first),
+ m_name(name),
+ m_func(func)
+ {
+ s_first = this;
+ }
+
+ TestFunc m_func;
+ const char* m_name;
+ TestRegister* m_next;
+
+ static TestRegister* s_first;
+};
+
+#define SLANG_UNIT_TEST(name, func) static TestRegister s_unitTest##__LINE__(name, func)
+
+enum class TestOutputMode
+{
+ eDefault = 0, ///< Default mode is to write test results to the console
+ eAppVeyor, ///< For AppVeyor continuous integration
+ eTravis, ///< We currently don't specialize for Travis, but maybe we should.
+ eXUnit, ///< xUnit original format https://nose.readthedocs.io/en/latest/plugins/xunit.html
+ eXUnit2, ///< https://xunit.github.io/docs/format-xml-v2
+};
+
+enum class TestResult
+{
+ eIgnored,
+ ePass,
+ eFail,
+};
+
+enum class TestMessageType
+{
+ eInfo, ///< General info (may not be shown depending on verbosity setting)
+ eTestFailure, ///< Describes how a test failure took place
+ eRunError, ///< Describes an error that caused a test not to actually correctly run
+};
+
+class TestContext
+{
+ public:
+
+ struct TestInfo
+ {
+ TestResult testResult = TestResult::eIgnored;
+ Slang::String name;
+ Slang::String message; ///< Message that is specific for the testResult
+ };
+
+ class Scope
+ {
+ public:
+ Scope(TestContext* context, const Slang::String& testName) :
+ m_context(context)
+ {
+ context->startTest(testName);
+ }
+ ~Scope()
+ {
+ m_context->endTest();
+ }
+
+ protected:
+ TestContext* m_context;
+ };
+
+ void startTest(const Slang::String& testName);
+ void addResult(TestResult result);
+ void addResultWithLocation(TestResult result, const char* testText, const char* file, int line);
+ void addResultWithLocation(bool testSucceeded, const char* testText, const char* file, int line);
+
+ void endTest();
+
+ /// Runs start/endTest and outputs the result
+ TestResult addTest(const Slang::String& testName, bool isPass);
+ /// Effectively runs start/endTest (so cannot be called inside start/endTest).
+ void addTest(const Slang::String& testName, TestResult testResult);
+
+ // Called for an error in the test-runner (not for an error involving a test itself).
+ void message(TestMessageType type, const Slang::String& errorText);
+ void messageFormat(TestMessageType type, char const* message, ...);
+
+ void dumpOutputDifference(const Slang::String& expectedOutput, const Slang::String& actualOutput);
+
+ /// True if can write output directly to stderr
+ bool canWriteStdError() const;
+
+ /// Call at end of tests
+ void outputSummary();
+
+ /// Returns true if all run tests succeeded
+ bool didAllSucceed() const;
+
+ /// Ctor
+ TestContext(TestOutputMode outputMode);
+
+ static TestResult combine(TestResult a, TestResult b) { return (a > b) ? a : b; }
+
+ static TestContext* get() { return s_context; }
+ static void set(TestContext* context) { s_context = context; }
+
+ Slang::List<TestInfo> m_testInfos;
+
+ int m_totalTestCount;
+ int m_passedTestCount;
+ int m_failedTestCount;
+ int m_ignoredTestCount;
+
+ int m_maxTestResults; ///< Maximum amount of results per test. If 0 it's infinite.
+
+ TestOutputMode m_outputMode = TestOutputMode::eDefault;
+ bool m_dumpOutputOnFailure;
+ bool m_isVerbose;
+
+protected:
+ void _addResult(const TestInfo& info);
+
+ Slang::StringBuilder m_currentMessage;
+ TestInfo m_currentInfo;
+ int m_numCurrentResults;
+
+ bool m_inTest;
+
+ static TestContext* s_context;
+};
+
+
diff --git a/tools/slang-test/unit-test-free-list.cpp b/tools/slang-test/unit-test-free-list.cpp
new file mode 100644
index 000000000..649c59571
--- /dev/null
+++ b/tools/slang-test/unit-test-free-list.cpp
@@ -0,0 +1,55 @@
+// unit-test-free-list.cpp
+
+#include "../../source/core/slang-free-list.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "test-context.h"
+
+#include "../../source/core/slang-random-generator.h"
+#include "../../source/core/list.h"
+
+using namespace Slang;
+
+static void freeListUnitTest()
+{
+ 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.Count()) < numDealloc ? int(allocs.Count()) : numDealloc;
+
+ for (int j = 0; j < numDealloc; j++)
+ {
+ const int index = randGen.nextInt32UpTo(int(allocs.Count()));
+
+ int* alloc = allocs[index];
+
+ SLANG_CHECK(*alloc <= i);
+ SLANG_CHECK(*alloc >= 0);
+
+ freeList.deallocate(alloc);
+
+ allocs.FastRemoveAt(index);
+ }
+ }
+}
+
+SLANG_UNIT_TEST("FreeList", freeListUnitTest); \ No newline at end of file
diff --git a/tools/slang-test/unit-test-memory-arena.cpp b/tools/slang-test/unit-test-memory-arena.cpp
new file mode 100644
index 000000000..69eed520a
--- /dev/null
+++ b/tools/slang-test/unit-test-memory-arena.cpp
@@ -0,0 +1,242 @@
+// unit-test-free-list.cpp
+
+#include "../../source/core/slang-memory-arena.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "test-context.h"
+
+#include "../../source/core/slang-random-generator.h"
+#include "../../source/core/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) ? ((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);
+}
+
+static void memoryArenaUnitTest()
+{
+ 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));
+
+ while (blocks.Count())
+ {
+ arena.deallocateLast(blocks.Last());
+ blocks.RemoveLast();
+ }
+ }
+
+ {
+
+ 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 < 1000; i++)
+ {
+ int var = randGen.nextInt32() & 0x3ff;
+ if (var < 3 && blocks.Count() > 0)
+ {
+ if (var == 0)
+ {
+ // Do a single dealloc
+ arena.deallocateLast(blocks.Last().m_data);
+ blocks.RemoveLast();
+ }
+ else if (var == 1)
+ {
+ // Deallocate everything
+ arena.deallocateAll();
+ blocks.Clear();
+ }
+ else
+ {
+ // Do a multiple dealloc
+ int index = randGen.nextInt32UpTo(int(blocks.Count()));
+
+ // Deallocate all afterwards
+ arena.deallocateAllFrom(blocks[index].m_data);
+
+ blocks.SetSize(index);
+ }
+ }
+ else
+ {
+ size_t sizeInBytes = (randGen.nextInt32() & 255) + 1;
+
+ // Lets go for an oversized block
+ if ((randGen.nextInt32() & 0xff) < 2)
+ {
+ sizeInBytes += blockSize;
+ }
+
+ 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 (int j = 0; j < int(blocks.Count()); ++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));
+ }
+ }
+ }
+ }
+}
+
+SLANG_UNIT_TEST("MemoryArena", memoryArenaUnitTest); \ No newline at end of file