/*
	$Id: BlockCacheConcurrencyTest.cpp,v 1.1 2003/09/07 11:53:03 bonefish Exp $
	
	This file tests BBlockCache from multiple threads to ensure there are
	no concurrency problems.
	
	*/


#include "BlockCacheConcurrencyTest.h"
#include "ThreadedTestCaller.h"
#include <BlockCache.h>
#include <List.h>


/*
 *  Method: BlockCacheConcurrencyTest::BlockCacheConcurrencyTest()
 *   Descr: This method is the only constructor for the BlockCacheConcurrencyTest
 *          class.
 */
		

	BlockCacheConcurrencyTest::BlockCacheConcurrencyTest(std::string name) : 
		BThreadedTestCase(name),
		theObjCache(NULL),
		theMallocCache(NULL),
		numBlocksInCache(128),
		sizeOfBlocksInCache(23),
		sizeOfNonCacheBlocks(29)
{
	}


/*
 *  Method: BlockCacheConcurrencyTest::~BlockCacheConcurrencyTest()
 *   Descr: This method is the destructor for the BlockCacheConcurrencyTest class.
 */


	BlockCacheConcurrencyTest::~BlockCacheConcurrencyTest()
{
}
		

/*
 *  Method:  BlockCacheConcurrencyTest::setUp()
 *   Descr:  This method creates a couple of BBlockCache instances to perform
 *           tests on.  One uses new/delete and the other uses malloc/free
 *           on its blocks.
 */
	

	void BlockCacheConcurrencyTest::setUp(void)
{
	theObjCache = new BBlockCache(numBlocksInCache, sizeOfBlocksInCache,
								  B_OBJECT_CACHE);
	theMallocCache = new BBlockCache(numBlocksInCache, sizeOfBlocksInCache,
								  	 B_MALLOC_CACHE);
}
		

/*
 *  Method:  BlockCacheConcurrencyTest::tearDown()
 *   Descr:  This method cleans up the BBlockCache instances which were tested.
 */
	

	void BlockCacheConcurrencyTest::tearDown(void)
{
	delete theObjCache;
	delete theMallocCache;
}


/*
 *  Method:  BlockCacheConcurrencyTest::GetBlock()
 *   Descr:  This method returns a pointer from the BBlockCache, checking
 *           the value before passing it to the caller.
 */
	

	void *BlockCacheConcurrencyTest::GetBlock(BBlockCache *theCache,
	                                          size_t blockSize,
	                                          thread_id theThread,
				                              BList *cacheList,
				                              BList *nonCacheList)
{
	void *thePtr = theCache->Get(blockSize);
	
	// The new block should not already be used by this thread.
	assert(!cacheList->HasItem(thePtr));
	assert(!nonCacheList->HasItem(thePtr));
	
	// Add the block to the list of blocks used by this thread.
	if (blockSize == sizeOfBlocksInCache) {
		assert(cacheList->AddItem(thePtr));
	} else {
		assert(nonCacheList->AddItem(thePtr));
	}
	
	// Store the thread id at the start of the block for future
	// reference.
	*((thread_id *)thePtr) = theThread;
	return(thePtr);
}


/*
 *  Method:  BlockCacheConcurrencyTest::SavedCacheBlock()
 *   Descr:  This method passes the pointer back to the BBlockCache
 *           and checks the sanity of the lists.
 */
	

	void BlockCacheConcurrencyTest::SaveBlock(BBlockCache *theCache,
	                                          void *thePtr,
	                                          size_t blockSize,
	                                          thread_id theThread,
	                                          BList *cacheList,
	                                          BList *nonCacheList)
{
	// The block being returned to the cache should still have
	// the thread id of this thread in it, or some other thread has
	// perhaps manipulated this block which would indicate a
	// concurrency problem.
	assert(*((thread_id *)thePtr) == theThread);
	
	// Remove the item from the appropriate list and confirm it isn't
	// on the other list for some reason.
	if (blockSize == sizeOfBlocksInCache) {
		assert(cacheList->RemoveItem(thePtr));
		assert(!nonCacheList->HasItem(thePtr));
	} else {
		assert(!cacheList->HasItem(thePtr));
		assert(nonCacheList->RemoveItem(thePtr));
	}
	theCache->Save(thePtr, blockSize);
}


/*
 *  Method:  BlockCacheConcurrencyTest::FreeBlock()
 *   Descr:  This method frees the block directly using delete[] or free(),
 *           checking the sanity of the lists as it does the operation.
 */
	

	void BlockCacheConcurrencyTest::FreeBlock(void *thePtr,
	                                          size_t blockSize,
	                                          bool isMallocTest,
	                                          thread_id theThread,
	                                          BList *cacheList,
	                                          BList *nonCacheList)
{
	// The block being returned to the cache should still have
	// the thread id of this thread in it, or some other thread has
	// perhaps manipulated this block which would indicate a
	// concurrency problem.
	assert(*((thread_id *)thePtr) == theThread);
	
	// Remove the item from the appropriate list and confirm it isn't
	// on the other list for some reason.
	if (blockSize == sizeOfBlocksInCache) {
		assert(cacheList->RemoveItem(thePtr));
		assert(!nonCacheList->HasItem(thePtr));
	} else {
		assert(!cacheList->HasItem(thePtr));
		assert(nonCacheList->RemoveItem(thePtr));
	}
	if (isMallocTest) {
		free(thePtr);
	} else {
		delete[] thePtr;
	}
}


/*
 *  Method:  BlockCacheConcurrencyTest::TestBlockCache()
 *   Descr:  This method performs the tests on BBlockCache.  It is
 *           called by 6 threads concurrently.  Three of them are
 *           operating on the B_OBJECT_CACHE instance of BBlockCache
 *           and the other three are operating on the B_MALLOC_CACHE
 *           instance.
 *
 *           The goal of this method is to perform a series of get,
 *           save and free operations on block from the cache using
 *           "cache size" and "non-cache size" blocks.  Also, at the
 *           end of this method, all blocks unfreed by this method are
 *           freed to avoid a memory leak.
 */
	

	void BlockCacheConcurrencyTest::TestBlockCache(BBlockCache *theCache,
												   bool isMallocTest)
{
	BList cacheList;
	BList nonCacheList;
	thread_id theThread = find_thread(NULL);

	// Do everything eight times to ensure the test runs long
	// enough to check for concurrency problems.
	for (int j = 0; j < 8; j++) {
		// Perform a series of gets, saves and frees
		for (int i = 0; i < numBlocksInCache / 2; i++) {
			GetBlock(theCache, sizeOfBlocksInCache, theThread, &cacheList, &nonCacheList);
			GetBlock(theCache, sizeOfBlocksInCache, theThread, &cacheList, &nonCacheList);
			GetBlock(theCache, sizeOfNonCacheBlocks, theThread, &cacheList, &nonCacheList);
			GetBlock(theCache, sizeOfNonCacheBlocks, theThread, &cacheList, &nonCacheList);
			
			SaveBlock(theCache, cacheList.ItemAt(cacheList.CountItems() / 2),
			          sizeOfBlocksInCache, theThread, &cacheList, &nonCacheList);
			SaveBlock(theCache, nonCacheList.ItemAt(nonCacheList.CountItems() / 2),
			          sizeOfNonCacheBlocks, theThread, &cacheList, &nonCacheList);
			
			GetBlock(theCache, sizeOfBlocksInCache, theThread, &cacheList, &nonCacheList);
			GetBlock(theCache, sizeOfBlocksInCache, theThread, &cacheList, &nonCacheList);
			GetBlock(theCache, sizeOfNonCacheBlocks, theThread, &cacheList, &nonCacheList);
			GetBlock(theCache, sizeOfNonCacheBlocks, theThread, &cacheList, &nonCacheList);
			
			FreeBlock(cacheList.ItemAt(cacheList.CountItems() / 2),
			          sizeOfBlocksInCache, isMallocTest, theThread, &cacheList, &nonCacheList);
			FreeBlock(nonCacheList.ItemAt(nonCacheList.CountItems() / 2),
			          sizeOfNonCacheBlocks, isMallocTest, theThread, &cacheList, &nonCacheList);
			}
		bool performFree = false;
		// Free or save (every other block) for all "cache sized" blocks.
		while (!cacheList.IsEmpty()) {
			if (performFree) {
				FreeBlock(cacheList.LastItem(), sizeOfBlocksInCache, isMallocTest, theThread, &cacheList,
				          &nonCacheList);
			} else {
				SaveBlock(theCache, cacheList.LastItem(), sizeOfBlocksInCache, theThread, &cacheList,
				          &nonCacheList);
			}
			performFree = !performFree;
		}
		// Free or save (every other block) for all "non-cache sized" blocks.
		while (!nonCacheList.IsEmpty()) {
			if (performFree) {
				FreeBlock(nonCacheList.LastItem(), sizeOfNonCacheBlocks, isMallocTest, theThread, &cacheList,
				          &nonCacheList);
			} else {
				SaveBlock(theCache, nonCacheList.LastItem(), sizeOfNonCacheBlocks, theThread, &cacheList,
				          &nonCacheList);
			}
			performFree = !performFree;
		}
	}
}
		

/*
 *  Method:  BlockCacheConcurrencyTest::TestThreadMalloc()
 *   Descr:  This method passes the BBlockCache instance to TestBlockCache()
 *           where the instance will be tested.
 */
	

	void BlockCacheConcurrencyTest::TestThreadMalloc(void)
{
	TestBlockCache(theMallocCache, true);
}
		

/*
 *  Method:  BlockCacheConcurrencyTest::TestThreadObj()
 *   Descr:  This method passes the BBlockCache instance to TestBlockCache()
 *           where the instance will be tested.
 */
	

	void BlockCacheConcurrencyTest::TestThreadObj(void)
{
	TestBlockCache(theObjCache, false);
}


/*
 *  Method:  BlockCacheConcurrencyTest::suite()
 *   Descr:  This static member function returns a test caller for performing 
 *           the "BlockCacheConcurrencyTest" test.  The test caller
 *           is created as a ThreadedTestCaller with six independent threads.
 */


CppUnit::Test *BlockCacheConcurrencyTest::suite(void)
{	
	typedef BThreadedTestCaller <BlockCacheConcurrencyTest >
		BlockCacheConcurrencyTestCaller;

	BlockCacheConcurrencyTest *theTest = new BlockCacheConcurrencyTest("");
	BlockCacheConcurrencyTestCaller *threadedTest = new BlockCacheConcurrencyTestCaller("BBlockCache::Concurrency Test", theTest);
	threadedTest->addThread("A", &BlockCacheConcurrencyTest::TestThreadObj);
	threadedTest->addThread("B", &BlockCacheConcurrencyTest::TestThreadObj);
	threadedTest->addThread("C", &BlockCacheConcurrencyTest::TestThreadObj);
	threadedTest->addThread("D", &BlockCacheConcurrencyTest::TestThreadMalloc);
	threadedTest->addThread("E", &BlockCacheConcurrencyTest::TestThreadMalloc);
	threadedTest->addThread("F", &BlockCacheConcurrencyTest::TestThreadMalloc);
	return(threadedTest);
}
