Parcourir la source

Add tests for FrameAllocator and DataChunker

James Urquhart il y a 1 an
Parent
commit
7332dd6643

+ 32 - 1
Engine/source/core/dataChunker.h

@@ -39,6 +39,8 @@ public:
       ChunkSize = 16384
    };
 
+   typedef T AlignmentType;
+
    struct alignas(uintptr_t) DataBlock : public AlignedBufferAllocator<T>
    {
       DataBlock* mNext;
@@ -129,6 +131,19 @@ public:
       AssertFatal(mChunkHead == NULL, "Tried setting AFTER init");
       mChunkSize = size;
    }
+
+   bool isManagedByChunker(void* ptr) const
+   {
+      U8* chkPtr = (U8*)ptr;
+      for (DataBlock* itr = mChunkHead; itr; itr = itr->mNext)
+      {
+         const U8* blockStart = (U8*)itr->getAlignedBuffer();
+         const U8* blockEnd = (U8*)itr->getAlignedBufferEnd();
+         if (chkPtr >= blockStart && chkPtr < blockEnd)
+            return true;
+      }
+      return false;
+   }
 };
 
 class DataChunker : public BaseDataChunker<uintptr_t>
@@ -166,6 +181,8 @@ public:
 class MultiTypedChunker : private BaseDataChunker<uintptr_t>
 {
 public:
+   typedef uintptr_t AlignmentType;
+
    MultiTypedChunker(dsize_t size = BaseDataChunker<uintptr_t>::ChunkSize) : BaseDataChunker<uintptr_t>(std::max<uintptr_t>(sizeof(uintptr_t), size))
    {
    }
@@ -195,7 +212,7 @@ template<class T> struct ChunkerFreeClassList
       mNextList = NULL;
    }
 
-   bool isEmpty()
+   bool isEmpty() const
    {
       return mNextList == NULL;
    }
@@ -248,7 +265,15 @@ public:
    void freeBlocks(bool keepOne = false)
    {
       BaseDataChunker<T>::freeBlocks(keepOne);
+      mFreeListHead.reset();
+   }
+
+   inline bool isManagedByChunker(void* ptr) const
+   {
+      return BaseDataChunker<T>::isManagedByChunker(ptr);
    }
+
+   inline ChunkerFreeClassList<T>& getFreeListHead() { return mFreeListHead; }
 };
 
 /// Implements a chunker which uses the data of another BaseDataChunker 
@@ -390,6 +415,8 @@ public:
       default:
          break;
       }
+
+      item.ptr = NULL;
    }
 
    void freeBlocks(bool keepOne = false)
@@ -398,4 +425,8 @@ public:
       mT2.freeBlocks(keepOne);
       mT3.freeBlocks(keepOne);
    }
+
+   inline ClassChunker<K1>& getT1Chunker() { return mT1; }
+   inline ClassChunker<K2>& getT2Chunker() { return mT2; }
+   inline ClassChunker<K3>& getT3Chunker() { return mT3; }
 };

+ 7 - 4
Engine/source/core/frameAllocator.cpp

@@ -29,15 +29,18 @@ thread_local FrameAllocator::FrameAllocatorType   FrameAllocator::smMainInstance
 thread_local dsize_t   FrameAllocator::smAllocatedBytes;
 #endif
 
-#if defined(TORQUE_DEBUG)
+U32 FrameAllocator::smMaxFrameAllocation;
 
-dsize_t FrameAllocator::smMaxFrameAllocation;
+U32 FrameAllocator::getMaxFrameAllocation()
+{
+   return (S32)FrameAllocator::smMaxFrameAllocation;
+}
 
+#if defined(TORQUE_DEBUG)
 
 DefineEngineFunction(getMaxFrameAllocation, S32, (), , "")
 {
-   return (S32)FrameAllocator::smMaxFrameAllocation;
+   return (S32)FrameAllocator::getMaxFrameAllocation();
 }
 
-
 #endif

+ 15 - 3
Engine/source/core/frameAllocator.h

@@ -103,11 +103,21 @@ public:
       return (U32)(numBytes / sizeof(T));
    }
 
+   static inline U32 calcRequiredPaddedByteSize(const dsize_t numBytes)
+   {
+      return calcRequiredElementSize(numBytes) * sizeof(T);
+   }
+
    inline T* getAlignedBuffer() const
    {
       return mBuffer;
    }
 
+   inline T* getAlignedBufferEnd() const
+   {
+      return mBuffer + mHighWaterMark;
+   }
+
    inline U32 getPosition() const
    {
       return mWaterMark;
@@ -153,7 +163,7 @@ public:
 class FrameAllocator
 {
 public:
-   static dsize_t   smMaxFrameAllocation;
+   static U32   smMaxFrameAllocation;
 #ifdef TORQUE_MEM_DEBUG
    static thread_local dsize_t   smAllocatedBytes;
 #endif
@@ -220,6 +230,8 @@ public:
       return smMainInstance.getSizeBytes();
    }
 
+   static U32 getMaxFrameAllocation();
+
    static thread_local FrameAllocatorType smMainInstance;
 };
 
@@ -253,7 +265,7 @@ public:
       FrameAllocator::setWaterMark(mMarker);
    }
 
-   void* alloc(const U32 allocSize) const
+   void* alloc(const U32 allocSize)
    {
       return FrameAllocator::alloc(allocSize);
    }
@@ -336,7 +348,7 @@ public:
    const T& operator *() const { return *mMemory; }
 
    T** operator &() { return &mMemory; }
-   const T** operator &() const { return &mMemory; }
+   T* const * operator &() const { return &mMemory; }
 
    operator T* () { return mMemory; }
    operator const T* () const { return mMemory; }

+ 347 - 0
Engine/source/testing/dataChunkerTest.cpp

@@ -0,0 +1,347 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2023-2024 tgemit contributors.
+// See AUTHORS file and git repository for contributor information.
+//
+// SPDX-License-Identifier: MIT
+//-----------------------------------------------------------------------------
+
+#ifdef TORQUE_TESTS_ENABLED
+#include "testing/unitTesting.h"
+#include "core/dataChunker.h"
+
+struct TestClassChunkerStruct
+{
+   U32 value;
+   U32 value2;
+
+   TestClassChunkerStruct()
+   {
+      value  = 0xC001B33F;
+      value2 = 0x10101010;
+   }
+   
+   ~TestClassChunkerStruct()
+   {
+      value  = 0;
+      value2 = 0;
+   }
+};
+
+
+TEST(BaseDataChunkerTest, BaseDataChunker_Should_Function_Correctly)
+{
+   BaseDataChunker<TestClassChunkerStruct> testChunks(1024);
+   BaseDataChunker<U32> testChunk4(1024);
+   BaseDataChunker<U64> testChunk8(1024);
+
+   EXPECT_TRUE(testChunks.countUsedBlocks() == 0);
+   EXPECT_TRUE(testChunk4.countUsedBlocks() == 0);
+   EXPECT_TRUE(testChunk8.countUsedBlocks() == 0);
+
+   testChunks.alloc(1);
+   testChunk4.alloc(1);
+   testChunk8.alloc(1);
+
+   EXPECT_TRUE(testChunks.countUsedBlocks() == 1);
+   EXPECT_TRUE(testChunk4.countUsedBlocks() == 1);
+   EXPECT_TRUE(testChunk8.countUsedBlocks() == 1);
+
+   testChunks.alloc(1);
+   testChunk4.alloc(1);
+   testChunk8.alloc(1);
+
+   EXPECT_TRUE(testChunks.countUsedBlocks() == 1);
+   EXPECT_TRUE(testChunk4.countUsedBlocks() == 1);
+   EXPECT_TRUE(testChunk8.countUsedBlocks() == 1);
+
+   EXPECT_TRUE(testChunks.countUsedBytes() == (sizeof(TestClassChunkerStruct) * 2));
+   EXPECT_TRUE(testChunk4.countUsedBytes() == (sizeof(U32) * 2));
+   EXPECT_TRUE(testChunk8.countUsedBytes() == (sizeof(U64) * 2));
+
+   testChunks.freeBlocks(true);
+   testChunk4.freeBlocks(true);
+   testChunk8.freeBlocks(true);
+   
+   EXPECT_TRUE(testChunks.countUsedBlocks() == 1);
+   EXPECT_TRUE(testChunk4.countUsedBlocks() == 1);
+   EXPECT_TRUE(testChunk8.countUsedBlocks() == 1);
+   
+   testChunks.freeBlocks(false);
+   testChunk4.freeBlocks(false);
+   testChunk8.freeBlocks(false);
+   
+   EXPECT_TRUE(testChunks.countUsedBlocks() == 0);
+   EXPECT_TRUE(testChunk4.countUsedBlocks() == 0);
+   EXPECT_TRUE(testChunk8.countUsedBlocks() == 0);
+
+   testChunks.setChunkSize(sizeof(TestClassChunkerStruct));
+   testChunks.alloc(1);
+   EXPECT_TRUE(testChunks.countUsedBlocks() == 1);
+   testChunks.alloc(1);
+   EXPECT_TRUE(testChunks.countUsedBlocks() == 2);
+}
+
+TEST(DataChunkerTest, DataChunker_Should_Function_Correctly)
+{
+   DataChunker testChunk(1024);
+
+   testChunk.alloc(1024);
+
+   EXPECT_TRUE(testChunk.countUsedBlocks() == 1);
+
+   testChunk.alloc(1024);
+
+   EXPECT_TRUE(testChunk.countUsedBlocks() == 2);
+
+   testChunk.alloc(4096);
+   
+   EXPECT_TRUE(testChunk.countUsedBytes() == (1024+1024+4096));
+
+   EXPECT_TRUE(testChunk.countUsedBlocks() == 3);
+
+   testChunk.alloc(12);
+
+   EXPECT_TRUE(testChunk.countUsedBlocks() == 4);
+
+   testChunk.alloc(12);
+
+   EXPECT_TRUE(testChunk.countUsedBlocks() == 4);
+   
+   U32 reqEls = AlignedBufferAllocator<uintptr_t>::calcRequiredElementSize(12) * sizeof(uintptr_t);
+   
+   EXPECT_TRUE(testChunk.countUsedBytes() == (1024+1024+4096+reqEls+reqEls));
+
+   testChunk.freeBlocks(true);
+   EXPECT_TRUE(testChunk.countUsedBlocks() == 1);
+   testChunk.freeBlocks(false);
+   EXPECT_TRUE(testChunk.countUsedBlocks() == 0);
+
+   // Large block cases
+
+   testChunk.alloc(8192);
+   EXPECT_TRUE(testChunk.countUsedBlocks() == 1);
+   testChunk.freeBlocks(true);
+   EXPECT_TRUE(testChunk.countUsedBlocks() == 1);
+
+   testChunk.alloc(8192);
+   testChunk.alloc(1024);
+   EXPECT_TRUE(testChunk.countUsedBlocks() == 2);
+   testChunk.freeBlocks(true);
+   EXPECT_TRUE(testChunk.countUsedBlocks() == 1);
+   testChunk.freeBlocks(false);
+   EXPECT_TRUE(testChunk.countUsedBlocks() == 0);
+   
+   // Instead using the chunk size
+   
+   for (U32 i=0; i<8; i++)
+   {
+      testChunk.alloc(1024);
+   }
+   EXPECT_TRUE(testChunk.countUsedBlocks() == 8);
+   testChunk.freeBlocks(false);
+   EXPECT_TRUE(testChunk.countUsedBlocks() == 0);
+}
+
+TEST(ChunkerTest,Chunker_Should_Function_Correctly)
+{
+   Chunker<TestClassChunkerStruct> foo;
+   TestClassChunkerStruct* value = foo.alloc();
+   EXPECT_TRUE(value->value != 0xC001B33F);
+   EXPECT_TRUE(value->value2 != 0x10101010);
+   // Should otherwise just act like DataChunker
+}
+
+TEST(MultiTypedChunkerTest,MultiTypedChunker_Should_Function_Correctly)
+{
+   struct TVS1
+   {
+      int a;
+      int b;
+   };
+   struct TVS2
+   {
+      int a;
+      int b;
+      int c;
+   };
+   MultiTypedChunker chunker;
+   TVS1* v1 = chunker.alloc<TVS1>();
+   TVS2* v2 = chunker.alloc<TVS2>();
+   TVS2* v3 = chunker.alloc<TVS2>();
+
+   EXPECT_TRUE(((U8*)v2) - ((U8*)v1) == sizeof(TVS1));
+   EXPECT_TRUE(((U8*)v3) - ((U8*)v2) == AlignedBufferAllocator<MultiTypedChunker::AlignmentType>::calcRequiredPaddedByteSize(sizeof(TVS2)));
+}
+
+TEST(ChunkerFreeClassListTest,ChunkerFreeClassList_Should_Function_Correctly)
+{
+   TestClassChunkerStruct list[5];
+   ChunkerFreeClassList<TestClassChunkerStruct> freeListTest;
+   
+   // Push & pop works as expected
+   EXPECT_TRUE(freeListTest.isEmpty() == true);
+   freeListTest.push((ChunkerFreeClassList<TestClassChunkerStruct>*)&list[0]);
+   EXPECT_TRUE(freeListTest.isEmpty() == false);
+   freeListTest.push((ChunkerFreeClassList<TestClassChunkerStruct>*)&list[4]);
+   EXPECT_TRUE(freeListTest.pop() == &list[4]);
+   EXPECT_TRUE(freeListTest.pop() == &list[0]);
+   EXPECT_TRUE(freeListTest.pop() == NULL);
+   
+   // Reset clears list head
+   freeListTest.push((ChunkerFreeClassList<TestClassChunkerStruct>*)&list[4]);
+   freeListTest.reset();
+   EXPECT_TRUE(freeListTest.pop() == NULL);
+}
+
+
+TEST(FreeListChunkerTest, FreeListChunkerTest_Should_Function_Correctly)
+{
+   FreeListChunker<TestClassChunkerStruct> testFreeList;
+   
+   TestClassChunkerStruct* s1 = testFreeList.alloc();
+   TestClassChunkerStruct* s2 = testFreeList.alloc();
+   
+   // Allocation is sequential
+   EXPECT_TRUE(s2 > s1);
+   EXPECT_TRUE(((s2 - s1) == 1));
+   
+   testFreeList.free(s1);
+   
+   // But previous reallocations are reused
+   TestClassChunkerStruct* s3 = testFreeList.alloc();
+   TestClassChunkerStruct* s4 = testFreeList.alloc();
+   
+   EXPECT_TRUE(s1 == s3);
+   EXPECT_TRUE(((s4 - s2) == 1)); // continues from previous free alloc
+   
+   // Check sharing
+   
+   FreeListChunker<TestClassChunkerStruct> sharedChunker(testFreeList.getChunker());
+   
+   s2 = testFreeList.alloc();
+   EXPECT_TRUE(((s2 - s4) == 1));
+}
+
+TEST(ClassChunkerTest, ClassChunker_Should_Function_Correctly)
+{
+   ClassChunker<TestClassChunkerStruct> testClassList;
+   
+   TestClassChunkerStruct* s1 = testClassList.alloc();
+   TestClassChunkerStruct* s2 = testClassList.alloc();
+   
+   // Allocation is sequential
+   EXPECT_TRUE(s2 > s1);
+   EXPECT_TRUE(((s2 - s1) == 1));
+   
+   testClassList.free(s1);
+   EXPECT_TRUE(s1->value == 0);
+   EXPECT_TRUE(s1->value2 == 0);
+   
+   // But previous reallocations are reused
+   TestClassChunkerStruct* s3 = testClassList.alloc();
+   TestClassChunkerStruct* s4 = testClassList.alloc();
+   
+   EXPECT_TRUE(s1 == s3);
+   EXPECT_TRUE(((s4 - s2) == 1)); // continues from previous free alloc
+   
+   // Values should be initialized correctly for all allocs at this point
+   EXPECT_TRUE(s1->value == 0xC001B33F);
+   EXPECT_TRUE(s1->value2 == 0x10101010);
+   EXPECT_TRUE(s2->value == 0xC001B33F);
+   EXPECT_TRUE(s2->value2 == 0x10101010);
+   EXPECT_TRUE(s3->value == 0xC001B33F);
+   EXPECT_TRUE(s3->value2 == 0x10101010);
+   EXPECT_TRUE(s4->value == 0xC001B33F);
+   EXPECT_TRUE(s4->value2 == 0x10101010);
+   
+   // Should still be valid if using freeBlocks
+   testClassList.freeBlocks(true);
+   EXPECT_TRUE(s1->value == 0xC001B33F);
+   EXPECT_TRUE(s1->value2 == 0x10101010);
+   EXPECT_TRUE(s2->value == 0xC001B33F);
+   EXPECT_TRUE(s2->value2 == 0x10101010);
+   EXPECT_TRUE(s3->value == 0xC001B33F);
+   EXPECT_TRUE(s3->value2 == 0x10101010);
+   EXPECT_TRUE(s4->value == 0xC001B33F);
+   EXPECT_TRUE(s4->value2 == 0x10101010);
+}
+
+
+TEST(ThreeTieredChunkerTest,ThreeTieredChunker_Should_Function_Correctly)
+{
+   struct TThreeSA
+   {
+      uintptr_t a;
+   };
+   struct TThreeSB
+   {
+      uintptr_t a;
+      uintptr_t b;
+   };
+   struct TThreeSC
+   {
+      uintptr_t a;
+      uintptr_t b;
+      uintptr_t c;
+   };
+   struct TThreeSD
+   {
+      uintptr_t a;
+      uintptr_t b;
+      uintptr_t c;
+      uintptr_t d;
+   };
+   ThreeTieredChunker<TThreeSA, TThreeSB, TThreeSC> threeChunker;
+
+   // Alloc should alloc in the correct lists
+
+   auto h1 = threeChunker.alloc(sizeof(TThreeSA));
+   auto h2 = threeChunker.alloc(sizeof(TThreeSB));
+   auto h3 = threeChunker.alloc(sizeof(TThreeSC));
+   auto h4 = threeChunker.alloc(sizeof(TThreeSD));
+
+   EXPECT_TRUE(threeChunker.getT1Chunker().isManagedByChunker(h3.ptr) == false);
+   EXPECT_TRUE(threeChunker.getT2Chunker().isManagedByChunker(h3.ptr) == false);
+   EXPECT_TRUE(threeChunker.getT3Chunker().isManagedByChunker(h3.ptr) == true);
+   EXPECT_TRUE(h3.tier == 3);
+
+   EXPECT_TRUE(threeChunker.getT1Chunker().isManagedByChunker(h2.ptr) == false);
+   EXPECT_TRUE(threeChunker.getT2Chunker().isManagedByChunker(h2.ptr) == true);
+   EXPECT_TRUE(threeChunker.getT3Chunker().isManagedByChunker(h2.ptr) == false);
+   EXPECT_TRUE(h2.tier == 2);
+
+   EXPECT_TRUE(threeChunker.getT1Chunker().isManagedByChunker(h1.ptr) == true);
+   EXPECT_TRUE(threeChunker.getT2Chunker().isManagedByChunker(h1.ptr) == false);
+   EXPECT_TRUE(threeChunker.getT3Chunker().isManagedByChunker(h1.ptr) == false);
+   EXPECT_TRUE(h1.tier == 1);
+
+   EXPECT_TRUE(threeChunker.getT1Chunker().isManagedByChunker(h4.ptr) == false);
+   EXPECT_TRUE(threeChunker.getT2Chunker().isManagedByChunker(h4.ptr) == false);
+   EXPECT_TRUE(threeChunker.getT3Chunker().isManagedByChunker(h4.ptr) == false);
+   EXPECT_TRUE(h4.tier == 0);
+
+   threeChunker.free(h1);
+   threeChunker.free(h2);
+   threeChunker.free(h3);
+   threeChunker.free(h4);
+
+   EXPECT_TRUE(h1.ptr == NULL);
+   EXPECT_TRUE(h2.ptr == NULL);
+   EXPECT_TRUE(h3.ptr == NULL);
+   EXPECT_TRUE(h4.ptr == NULL);
+
+   // freeBlocks should also clear ALL the list heads
+
+   EXPECT_FALSE(threeChunker.getT1Chunker().getFreeListHead().isEmpty());
+   EXPECT_FALSE(threeChunker.getT2Chunker().getFreeListHead().isEmpty());
+   EXPECT_FALSE(threeChunker.getT3Chunker().getFreeListHead().isEmpty());
+
+   threeChunker.freeBlocks(false);
+
+   EXPECT_TRUE(threeChunker.getT1Chunker().getFreeListHead().isEmpty());
+   EXPECT_TRUE(threeChunker.getT2Chunker().getFreeListHead().isEmpty());
+   EXPECT_TRUE(threeChunker.getT3Chunker().getFreeListHead().isEmpty());
+}
+
+
+#endif

+ 195 - 0
Engine/source/testing/frameAllocatorTest.cpp

@@ -0,0 +1,195 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2023-2024 tgemit contributors.
+// See AUTHORS file and git repository for contributor information.
+//
+// SPDX-License-Identifier: MIT
+//-----------------------------------------------------------------------------
+
+#ifdef TORQUE_TESTS_ENABLED
+#include "testing/unitTesting.h"
+#include "core/frameAllocator.h"
+
+struct TestAlignmentStruct
+{
+   U64 values[4];
+};
+
+TEST(AlignedBufferAllocatorTest, AlignedBufferAllocator_Should_Function_Correctly)
+{
+   AlignedBufferAllocator<U32> ba4;
+   AlignedBufferAllocator<U64> ba8;
+   AlignedBufferAllocator<TestAlignmentStruct> bas;
+   
+   const U32 bufSize32 = (sizeof(TestAlignmentStruct) / 4) * 20;
+   U32 testAlignmentBuffer[bufSize32];
+   for (U32 i=0; i<bufSize32; i++)
+   {
+      testAlignmentBuffer[i] = i;
+   }
+   
+   EXPECT_TRUE(ba4.calcRequiredElementSize(20) == 5);
+   EXPECT_TRUE(ba8.calcRequiredElementSize(20) == 3);
+   EXPECT_TRUE(bas.calcRequiredElementSize(20) == 1);
+   EXPECT_TRUE(bas.calcRequiredElementSize(32) == 1);
+   EXPECT_TRUE(bas.calcRequiredElementSize(33) == 2);
+   EXPECT_TRUE(bas.calcRequiredElementSize(64) == 2);
+   
+   
+   EXPECT_TRUE(ba4.calcMaxElementSize(20) == 5);
+   EXPECT_TRUE(ba8.calcMaxElementSize(20) == 2);
+   EXPECT_TRUE(bas.calcMaxElementSize(20) == 0);
+   EXPECT_TRUE(bas.calcMaxElementSize(32) == 1);
+   EXPECT_TRUE(bas.calcMaxElementSize(33) == 1);
+   EXPECT_TRUE(bas.calcMaxElementSize(64) == 2);
+   
+   ba4.initWithBytes((U32*)testAlignmentBuffer, sizeof(testAlignmentBuffer));
+   ba8.initWithBytes((U64*)testAlignmentBuffer,  sizeof(testAlignmentBuffer));
+   bas.initWithBytes((TestAlignmentStruct*)testAlignmentBuffer,  sizeof(testAlignmentBuffer));
+   
+   EXPECT_TRUE(ba4.getElementsLeft() == 160);
+   EXPECT_TRUE(ba8.getElementsLeft() == 80);
+   EXPECT_TRUE(bas.getElementsLeft() == 20);
+   
+   EXPECT_TRUE(ba4.getSizeBytes() == 640);
+   EXPECT_TRUE(ba8.getSizeBytes() == 640);
+   EXPECT_TRUE(bas.getSizeBytes() == 640);
+   
+   EXPECT_TRUE(ba4.allocElements(1) == &testAlignmentBuffer[0]);
+   EXPECT_TRUE(ba4.getPosition() == 1);
+   EXPECT_TRUE(ba4.getPositionBytes() == 4);
+   EXPECT_TRUE(ba4.getElementsLeft() == 159);
+   
+   EXPECT_TRUE(ba4.allocElements(7) == &testAlignmentBuffer[1]);
+   EXPECT_TRUE(ba4.getPosition() == 8);
+   EXPECT_TRUE(ba4.getPositionBytes() == 32);
+   EXPECT_TRUE(ba4.getElementsLeft() == 152);
+   
+   ba4.setPosition(100);
+   
+   EXPECT_TRUE(ba4.allocElements(1) == &testAlignmentBuffer[100]);
+   EXPECT_TRUE(ba4.getPosition() == 101);
+   EXPECT_TRUE(ba4.getPositionBytes() == 404);
+   EXPECT_TRUE(ba4.getElementsLeft() == 59);
+   
+   ba4.setPosition(160);
+   EXPECT_TRUE(ba4.allocElements(1) == NULL);
+   EXPECT_TRUE(ba4.getPosition() == 160);
+   EXPECT_TRUE(ba4.getPositionBytes() == (160*4));
+   EXPECT_TRUE(ba4.getElementsLeft() == 0);
+}
+
+TEST(FrameAllocatorTest, FrameAllocator_Should_Function_Correctly)
+{
+   // NOTE: assuming alloc and destroy already work
+   
+   EXPECT_TRUE(FrameAllocator::getWaterMark() == 0);
+   FrameAllocator::setWaterMark(100);
+   EXPECT_TRUE(FrameAllocator::getWaterMark() == 100);
+   FrameAllocator::setWaterMark(104);
+   EXPECT_TRUE(FrameAllocator::getWaterMark() == 104);
+   
+   FrameAllocator::alloc(1);
+   EXPECT_TRUE(FrameAllocator::getWaterMark() == 108);
+   FrameAllocator::alloc(5);
+   EXPECT_TRUE(FrameAllocator::getWaterMark() == 116);
+   
+   FrameAllocator::setWaterMark(0);
+   FrameAllocator::alloc(1);
+   EXPECT_TRUE(FrameAllocator::getWaterMark() == 4);
+   
+   FrameAllocator::setWaterMark(0);
+}
+
+
+TEST(FrameAllocatorMarker, FrameAllocatorMarker_Should_Function_Correctly)
+{
+   U32 markerValue = 0;
+   FrameAllocator::setWaterMark(8);
+
+   // Marker should act as a bookmark for the FrameAllocator
+   {
+      FrameAllocatorMarker marker;
+      FrameAllocator::alloc(100);
+      markerValue = FrameAllocator::getWaterMark();
+      EXPECT_TRUE(markerValue != 8);
+      marker.alloc(4);
+      EXPECT_TRUE(markerValue != FrameAllocator::getWaterMark());
+   }
+
+   // Going out of scope sets watermark
+   EXPECT_TRUE(FrameAllocator::getWaterMark() == 8);
+}
+
+static U32 gFTDestructTest = 0;
+
+TEST(FrameTempTest, FrameTempShould_Function_Correctly)
+{
+   FrameAllocator::setWaterMark(0);
+   {
+      FrameTemp<TestAlignmentStruct> fooTemp(20);
+      EXPECT_TRUE(FrameAllocator::getWaterMark() == sizeof(TestAlignmentStruct)*20);
+      EXPECT_TRUE(&fooTemp[0] == fooTemp.address());
+      EXPECT_TRUE((&fooTemp[1] - &fooTemp[0]) == 1);
+      EXPECT_TRUE(fooTemp.getObjectCount() == 20);
+      EXPECT_TRUE(fooTemp.size() == 20);
+
+      const FrameTemp<TestAlignmentStruct>& fooC = fooTemp;
+      EXPECT_TRUE(&fooC[0] == fooC.address());
+      EXPECT_TRUE((&fooC[1] - &fooC[0]) == 1);
+      EXPECT_TRUE(fooC.getObjectCount() == 20);
+      EXPECT_TRUE(fooC.size() == 20);
+
+      // Accessors should work
+
+      // Call the overloaded operators by name
+      TestAlignmentStruct& value = fooTemp.operator*();
+      TestAlignmentStruct** ptr = fooTemp.operator&();
+      const TestAlignmentStruct* constPtr = fooTemp.operator const TestAlignmentStruct * ();
+      TestAlignmentStruct& ref = fooTemp.operator TestAlignmentStruct & ();
+      const TestAlignmentStruct& constRef = fooTemp.operator const TestAlignmentStruct & ();
+      TestAlignmentStruct copy = fooTemp.operator TestAlignmentStruct();
+
+      EXPECT_TRUE(*ptr == fooTemp.address());
+      EXPECT_TRUE(&value == fooTemp.address());
+      EXPECT_TRUE(constPtr == fooTemp.address());
+      EXPECT_TRUE(&ref == fooTemp.address());
+      EXPECT_TRUE(&constRef == fooTemp.address());
+      EXPECT_TRUE(&copy != fooTemp.address());
+
+      // Same for fooC
+      const TestAlignmentStruct& Cvalue = fooC.operator*();
+      TestAlignmentStruct* const* Cptr = fooC.operator&();
+      const TestAlignmentStruct* CconstPtr = fooC.operator const TestAlignmentStruct * ();
+      const TestAlignmentStruct& CconstRef = fooC.operator const TestAlignmentStruct & ();
+
+      EXPECT_TRUE(*Cptr == fooC.address());
+      EXPECT_TRUE(&Cvalue == fooC.address());
+      EXPECT_TRUE(CconstPtr == fooC.address());
+      EXPECT_TRUE(&CconstRef == fooC.address());
+      EXPECT_TRUE(&ref == fooC.address());
+      EXPECT_TRUE(&constRef == fooC.address());
+   }
+
+   // Exiting scope sets watermark
+   EXPECT_TRUE(FrameAllocator::getWaterMark() == 0);
+
+   // Test the destructor actually gets called
+
+   struct FTDestructTest
+   {
+      ~FTDestructTest()
+      {
+         gFTDestructTest++;
+      }
+   };
+
+   {
+      gFTDestructTest = 0;
+      FrameTemp<FTDestructTest> foo2Temp(10);
+   }
+
+   EXPECT_TRUE(gFTDestructTest == 10);
+}
+
+
+#endif