/*****************************************************************************
 * $Id: shared_ptr.cc,v 1.1 2006/10/21 00:19:08 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Test of SharedPtr template class.
 * @remarks	SharedPtr default constructor now creates a SharedPtr that
 *          points to nothing.
 *//*
 * LEGAL:	COPYRIGHT (C) 2004 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 *****************************************************************************/

#define TESTING 1
#include "test.hh"
#include "gfx_shared_ptr.hh"

namespace gfx {

/*****************************************************************************
 * Test.
 *****************************************************************************/
struct Counts
{
	int object;				// inc'd by constructor, dec'd by destructor
	int constructor;
	int destructor;
	int num;

	void Clear( void )
	{
		object = constructor = destructor = num = 0;
	}
};

ostream&
operator<<( ostream& strm, const Counts& obj )
{
	strm << "counts: "
         << "object = "		<< obj.object			<< " "
         << "constr = "		<< obj.constructor		<< " "
         << "destr  = "		<< obj.destructor		<< "\n";
    return strm;
}

static Counts counts;

const int TEST_VAL = 0xabcd;
const int MAGIC = 0x198dcb1;

class Object
{
public:
	Object( void )
	{
		++counts.object;
		++counts.constructor;
		mVal = counts.num++;
		mTestVal = TEST_VAL;
	}

	Object( int val )
	{
		++counts.object;
		++counts.constructor;
		mVal = counts.num++;
		mTestVal = val;
	}

	~Object()
	{
		--counts.object;
		++counts.destructor;
	}	

	int Magic( void ) { return MAGIC; }

	int mVal;
	int mTestVal;
};

bool
FuncSharedPtrAutovar( SharedPtr<Object>& shared, int expectedRefCnt, int expectedObjCnt )
{
	SharedPtr<Object> shared2 = shared;
	if ( *shared.mRefCntPtr != expectedRefCnt ) Fail();
	if ( counts.object != expectedObjCnt ) Fail();
	// shared2 is auto-deleted
	return true;
}

class ClassWith1SharedPtr
{
public:
    ClassWith1SharedPtr( void )
    :   mSharedPtr1(NULL)
    {
    }

public:
    SharedPtr<Object> mSharedPtr1;
};

class ClassWith3SharedPtrs
{
public:
    ClassWith3SharedPtrs( void )
    :   mSharedPtr1(NULL),
        mSharedPtr2(new Object(102)),
        mSharedPtr3(new Object(103))
    {
    }

public:
    SharedPtr<Object> mSharedPtr1;
    SharedPtr<Object> mSharedPtr2;
    SharedPtr<Object> mSharedPtr3;
};

bool
TestSharedPtr( void )
{
 // ----------------------------
 // Test SharedPtr default constructor.
 // It shouldn't create an underlying object.
 {
	cerr << "Testing SharedPtr default constructor...\n";
	counts.Clear();

	SharedPtr<Object> nil;
	if ( nil.mObj != NULL ) Fail();         // pointer should be NULL
	if ( nil.mRefCntPtr == NULL ) Fail();   // ref cnt should point to 1 (NULL is reference-counted)
    if ( *nil.mRefCntPtr != 1 ) Fail();
	if ( counts.object != 0 ) Fail();
	if ( counts.constructor != 0 ) Fail();

    // <const T> should compile as should <T>.
    SharedPtr<const Object> ptr_const;
 }
 // ----------------------------


 // ----------------------------
 // Test SharedPtrs created as auto-vars.
 {
	cerr << "Testing SharedPtr (auto var) with 3 references using copy constr...\n";
	counts.Clear();

	Object* obj = new Object;
	SharedPtr<Object> shared( obj );            // same/shared object
	SharedPtr<Object> shared2( shared );        // same/shared object
	SharedPtr<Object> shared3( shared );        // same/shared object

	if ( *shared.mRefCntPtr  != 3 ) Fail();		// 3 references to one shared object
	if ( *shared2.mRefCntPtr != 3 ) Fail();
	if ( *shared3.mRefCntPtr != 3 ) Fail();

	if ( shared  != obj ) Fail();  // test SharedPtr::operator!=()
	if ( shared2 != obj ) Fail();
	if ( shared3 != obj ) Fail();
	if ( ! (shared  == obj) ) Fail();  // test SharedPtr::operator==()
	if ( ! (shared2 == obj) ) Fail();
	if ( ! (shared3 == obj) ) Fail();

    // Test operator==/!= with identical SharedPtrs.
    if ( shared != shared2 ) Fail();
    if ( ! (shared == shared2) ) Fail();

	// Test accessing Object via SharedPtr.
	if ( GET_PTR(shared)->mTestVal != TEST_VAL ) Fail();
	if ( GET_PTR(shared)->Magic() != MAGIC ) Fail();
	if ( shared->mTestVal != TEST_VAL ) Fail();
	if ( shared->Magic() != MAGIC ) Fail();
	if ( (*shared).mTestVal != TEST_VAL ) Fail();
	if ( (*shared).Magic() != MAGIC ) Fail();

	if ( counts.object != 1 ) Fail();
	if ( counts.constructor != 1 ) Fail();
	if ( counts.destructor != 0 ) Fail();
 }
    // SharedPtr auto-vars were deleted, check.
	if ( counts.object != 0 ) Fail();		// counts should be the same
	if ( counts.destructor != 1 ) Fail();
 // ----------------------------


 // ----------------------------
 // Test SharedPtrs created by new (pointer to SharedPtr).
 // (This form is useless outside testing: SharedPtrs themselves won't be pointers in practice)
 {
	cerr << "Testing SharedPtr (ptr) with 2 references using copy constr...\n";
	counts.Clear();

	Object* obj = new Object;
	SharedPtr<Object>* shared  = new SharedPtr<Object>( obj );
	SharedPtr<Object>* shared2 = new SharedPtr<Object>( *shared );

	if ( *shared->mRefCntPtr  != 2 ) Fail();	// 2 references to one shared object
	if ( *shared2->mRefCntPtr != 2 ) Fail();

	if ( *shared  != obj ) Fail();       // test SharedPtr::operator!=(T*obj)
	if ( *shared2 != obj ) Fail();
	if ( ! (*shared  == obj) ) Fail();   // test SharedPtr::operator==(T*obj)
	if ( ! (*shared2 == obj) ) Fail();

	if ( counts.object != 1 ) Fail();
	if ( counts.constructor != 1 ) Fail();
	if ( counts.destructor != 0 ) Fail();

	delete shared2;
	if ( *shared->mRefCntPtr != 1 ) Fail();	// reference count should be 1
	if ( counts.object != 1 ) Fail();		// counts should be the same
	if ( counts.constructor != 1 ) Fail();
	if ( counts.destructor != 0 ) Fail();

	delete shared;
	if ( counts.object != 0 ) Fail();		// counts should be the same
	if ( counts.destructor != 1 ) Fail();
 }
 // ----------------------------


 // ----------------------------
 // Test SharedPtrs as class members.
 {
	cerr << "Testing SharedPtr as class members...\n";
	counts.Clear();

    // See ctor.
    ClassWith3SharedPtrs c3;
    if ( c3.mSharedPtr1 != NULL ) Fail();
    if ( c3.mSharedPtr2 == NULL ) Fail();
    if ( c3.mSharedPtr2->mTestVal != 102 ) Fail();
    if ( c3.mSharedPtr3 == NULL ) Fail();
    if ( c3.mSharedPtr3->mTestVal != 103 ) Fail();

    // Test copying a class with SharedPtrs.
    Object* obj = new Object;
    ClassWith1SharedPtr c1a;
    c1a.mSharedPtr1 = obj;  // refCnt=1
    ClassWith1SharedPtr c1b;
    c1b = c1a;              // refCnt=2
    if ( GET_PTR(c1a.mSharedPtr1) != obj ) Fail();
    if ( GET_PTR(c1b.mSharedPtr1) != obj ) Fail();
    if ( c1a.mSharedPtr1.mRefCntPtr != c1b.mSharedPtr1.mRefCntPtr ) Fail();
    if ( *c1a.mSharedPtr1.mRefCntPtr != 2 ) Fail();
    if ( *c1b.mSharedPtr1.mRefCntPtr != 2 ) Fail();
 }
 // ----------------------------


 // ----------------------------
 // Test SharedPtrs that point to different objects.
 {
	cerr << "Testing SharedPtr to different objects...\n";
	counts.Clear();

	SharedPtr<Object> shared( new Object );
	SharedPtr<Object> shared2( new Object );

	if ( *shared.mRefCntPtr  != 1 ) Fail();		// 3 references to one shared object
	if ( *shared2.mRefCntPtr != 1 ) Fail();

    // Test operator==/!= with SharedPtrs that point to different actual objects
    if ( shared == shared2 ) Fail();
    if ( ! (shared != shared2) ) Fail();
 }
 // ----------------------------


 // ----------------------------
 // Test SharedPtr with assignment operator.
 {
	cerr << "Testing SharedPtr with assignment operator...\n";
	counts.Clear();

	Object* obj = new Object;
	SharedPtr<Object> shared( obj );
	SharedPtr<Object> shared2 = shared;
	SharedPtr<Object> shared3 = shared2;
	SharedPtr<Object> shared4 = shared3;

	if ( *shared.mRefCntPtr  != 4 ) Fail();		// 3 references to one shared object
	if ( *shared2.mRefCntPtr != 4 ) Fail();
	if ( *shared3.mRefCntPtr != 4 ) Fail();

	if ( GET_PTR(shared)  != obj ) Fail();
	if ( GET_PTR(shared2) != obj ) Fail();
	if ( GET_PTR(shared2) != obj ) Fail();
	if ( GET_PTR(shared3) != obj ) Fail();

	if ( shared  != obj ) Fail();
	if ( shared2 != obj ) Fail();
	if ( shared2 != obj ) Fail();
	if ( shared3 != obj ) Fail();
	if ( ! (shared  == obj) ) Fail();
	if ( ! (shared2 == obj) ) Fail();
	if ( ! (shared2 == obj) ) Fail();
	if ( ! (shared3 == obj) ) Fail();

	if ( counts.object != 1 ) Fail();
	if ( counts.constructor != 1 ) Fail();
	if ( counts.destructor != 0 ) Fail();
 }
    // SharedPtr auto-vars were deleted, check.
	if ( counts.object != 0 ) Fail();		// counts should be the same
	if ( counts.destructor != 1 ) Fail();
 // ----------------------------


 // ----------------------------
 // Test passing as SharedPtr to a function which copies it.
 {
   {
	cerr << "Testing passing SharedPtr to a function...\n";
	counts.Clear();

	Object* obj = new Object;
	SharedPtr<Object> shared( obj );
	if ( *shared.mRefCntPtr != 1 ) Fail();
	if ( counts.object != 1 ) Fail();
	if ( counts.constructor != 1 ) Fail();
	if ( counts.destructor != 0 ) Fail();

	// This function has a SharedPtr auto var.
	// Reference count should increment to 2 then back to 1.
	if ( ! FuncSharedPtrAutovar( shared, 2, 1 ) ) Fail();
	if ( *shared.mRefCntPtr != 1 ) Fail();   // should increment to 2 then back to 1
	if ( counts.object != 1 ) Fail();
	if ( counts.constructor != 1 ) Fail();
	if ( counts.destructor != 0 ) Fail();
   }
	if ( counts.object != 0 ) Fail();
	if ( counts.constructor != 1 ) Fail();
	if ( counts.destructor != 1 ) Fail();
}
 // ----------------------------


 // ----------------------------
 // Test using SharedPtr with STL: vector<SharedPtr>.resize().
 // STL, depending on implementation, can call the SharedPtr's default and even copy constructor.
 {
   {
	cerr << "Testing vector<SharedPtr>.resize()...\n";
	counts.Clear();

	vector<SharedPtr<Object> > vec;
	const int cnt = 50;
	vec.resize( cnt );		// resize() is what really allocates elements
	vec.reserve( cnt );		// test this along the way
	if ( vec.size() != (uint)cnt ) Fail();

	Object* obj = new Object;
	SharedPtr<Object> shared( obj );

    for ( int i = 0; i < cnt; ++i )
	{
		vec[i] = shared;

		if ( vec[i] != shared ) Fail();
		if ( GET_CONST_PTR(vec[i]) != GET_CONST_PTR(shared) ) Fail();

		if ( *(shared.mRefCntPtr) != i+2 ) Fail();	// when i=0, ref cnt will be 2
		if ( *(vec[i].mRefCntPtr) != i+2 ) Fail();

		if ( vec[i]->Magic() != MAGIC ) Fail();
	}
	if ( *shared.mRefCntPtr != cnt+1 ) Fail();		// +1 for original "shared"

   }
}
 // ----------------------------


 // ----------------------------
 // Ensure that operator=(T*) is invoked rather than implict
 // conversion to SharedPtr<T> and invoking of operator=(SharedPtr<T)&)
 {
	cerr << "Testing SharedPtr<T>::operator=(T*)...\n";
	counts.Clear();

	Object* obj = new Object;
	SharedPtr<Object> shared  = obj;
	SharedPtr<Object> shared2 = shared;
	SharedPtr<Object> shared3 = shared;
	SharedPtr<Object> shared4 = shared;

    // Preliminary checks before testing operator=(T*).
	if ( *shared.mRefCntPtr  != 4 ) Fail();	// 4 references to one shared object
	if ( *shared2.mRefCntPtr != 4 ) Fail();

    // This should invoke operator=(T*) which does mObj=obj.
    // A bug is if T* is implicitly converted to a SharedPtr<T>
    // and instead operator=(SharedPtr<T>&) is applied!

    Object* obj2 = new Object;
    shared2 = obj2;

    if ( shared2.mObj != obj2 ) Fail();

    // shared2 should have a reference count of 1,
    // because it is the only SharedPtr pointing to obj2.

    if ( *shared2.mRefCntPtr != 1 ) Fail();

    // The other 3 SharedPtr should be affected,
    // and have ref cnt = 3.

    if ( *shared.mRefCntPtr  != 3 ) Fail();
    if ( *shared3.mRefCntPtr != 3 ) Fail();
    if ( *shared4.mRefCntPtr != 3 ) Fail();

    // Ref cnt pointer of shared2 should differ.
    if ( shared2.mRefCntPtr == shared.mRefCntPtr ) Fail();
    if ( shared2.mRefCntPtr == shared3.mRefCntPtr ) Fail();
    if ( shared2.mRefCntPtr == shared4.mRefCntPtr ) Fail();
 }
 // ----------------------------


 // ----------------------------
 // Yet more tests of operator=(SharedPtr<T)&)
 {
	cerr << "Testing SharedPtr<T>::operator=(SharedPtr<T>&)...\n";
	counts.Clear();

	Object* obj = new Object;
	SharedPtr<Object> shared  = obj;
	SharedPtr<Object> shared2 = shared;
	SharedPtr<Object> shared3 = shared2;

	if ( *shared.mRefCntPtr  != 3 ) Fail();	// 3 references to one shared object
	if ( *shared2.mRefCntPtr != 3 ) Fail();
    if ( *shared3.mRefCntPtr != 3 ) Fail();
 }
 // ----------------------------


	return true;
}

/*****************************************************************************
 * Test the SharedPtrArr template for sharing arrays.
 * Test that SharedPtrArr uses delete[].
 * This can be done by testig if delete is called repeatedly by delete[].
 *****************************************************************************/
class Object2
{
public:
	Object2() { mDestructorCnt = 0; }
	~Object2() { ++mDestructorCnt; }
	static int mDestructorCnt;
};
int Object2::mDestructorCnt;

bool
TestSharedPtrArr( void )
{
 {
	Object2* arr = new Object2[3];
	SharedPtrArr<Object2> sharr = arr;
	if ( Object2::mDestructorCnt != 0 ) Fail();
 }
	// SharedPtrArr was destroyed after leaving block scope,
	// so it should have deleted the underlying array,
	// and Object2::~Object2() should have been called 3 times.

	if ( Object2::mDestructorCnt != 3 ) Fail();

	return true;
}


} // namespace gfx

/*****************************************************************************
 * main.
 *****************************************************************************/
int
main( int argc, char** argv )
{
	GFX_INIT( argv[0] );
	int res = 1;
	if ( TestSharedPtr()        // test sharing individual objects
      && TestSharedPtrArr() )   // test sharing arrays
	{
		cerr << "Test PASSED. \n";
		res = 0;
	}
	else
	{
		cerr << "Test FAILED! \n";
		res = 1;
	}
	GFX_CLEANUP();
	exit( res );
}
