/*****************************************************************************
 * $LastChangedDate: 2011-04-23 21:07:07 -0400 (Sat, 23 Apr 2011) $
 * @file
 * @author  Jim E. Brooks  http://www.palomino3d.org
 * @brief   Test of shptr template class (excluding NULL variants, mostly).
 *//*
 * LEGAL:   COPYRIGHT (C) 2004 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE VERSION 2 (GPL 2).
 *****************************************************************************/

#define TESTING 1
#include <vector>
#include <list>
#include <algorithm>
#include "test.hh"
#include "base/module.hh"
using namespace base;
#include "test_shptr.hh"

class Int : public Shared
{
public:
                        Int( int i ) : mVal(i) { ++msInstanceCount; }
                        ~Int() { --msInstanceCount; }
    CLASS_METHOD void   Clear( void ) { msInstanceCount = 0; }
                        operator int() { return mVal; }
    int                 Get( void ) { return mVal; }

public:
    CLASS_VAR int   msInstanceCount;
    int             mVal;
};
int Int::msInstanceCount;

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;
    }
};

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

PERSISTENT Counts counts;

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

////////////////////////////////////////////////////////////////////////////////
//
class Object : public Shared
{
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;
};

int GetRefCount( const Object* obj ) { return obj->mRefCount; }
int GetRefCount( const Object& obj ) { return obj.mRefCount; }

// shptr is intended to be passed by value (not by reference).
// refCount : refCount prior to call
// objCount : objCount prior to call
bool
FuncPassedShptr( shptr<Object> shared, int refCount, int objCount )
{
    if ( GetRefCount(shared) != refCount + 1 ) return Fail();  // pass arg inc'd refcount
    if ( counts.object != objCount ) return Fail();

    shptr<Object> shared2 = shared;
    if ( shared2.PTR() != shared.PTR() ) return Fail();
    if ( GetRefCount(shared) != refCount + 2 ) return Fail();
    if ( counts.object != objCount ) return Fail();
    // shared2 is auto-deleted
    return true;
}

bool
FuncPassedShptrThatKeepsIt( shptr<Object> shared )
{
    PERSISTENT shptr<Object> sShared = shared;  // keep it
    return true;
}

shptr<Object>
FuncThatReturnsShptr( void )
{
    return new Object;
}

shptr<Object>
FuncThatReturnsShptr2( void )
{
    return shptr<Object>( new Object );
}

class ClassWith1Shptr
{
public:
    ClassWith1Shptr( void )
    :   mShptr1(new Object(101))
    {
    }

public:
    shptr<Object> mShptr1;
};

class ClassWith3Shptrs
{
public:
    ClassWith3Shptrs( void )
    :   mShptr1(new Object(101)),
        mShptr2(new Object(102)),
        mShptr3(new Object(103))
    {
    }

public:
    shptr<Object> mShptr1;
    shptr<Object> mShptr2;
    shptr<Object> mShptr3;
};

//--------------------------------------------------------------------------
// Test this habit of using a PERSISTENT shptr in lieue of a NULL shptr.
// Ensure that ~Shared()/~shptr() don't cause problem at program exit.
class NullObject : public Shared
{
};
shptr<NullObject> sNullObject = new NullObject;
//--------------------------------------------------------------------------

bool
TestShptr( void )
{
 // ----------------------------
 // Test shptr default constructor.
 // [2007/01] It should create an object using default ctor (it doesn't initialize as NULL).
 {
    CDEBUG << "Testing shptr default constructor...\n";
    counts.Clear();

    shptr<Object> nil;
    if ( counts.object != 1 ) return Fail();
    if ( counts.constructor != 1 ) return Fail();
    if ( nil.mObj == NULL ) return Fail();
    if ( nil.mObj->mTestVal != TEST_VAL ) return Fail();

    // <const T> should compile as should <T>.
    shptr<const Object> const_nil;
    if ( counts.object != 2 ) return Fail();
    if ( counts.constructor != 2 ) return Fail();
    if ( const_nil.mObj == NULL ) return Fail();
    if ( const_nil.mObj->mTestVal != TEST_VAL ) return Fail();
 }
 // ----------------------------


 // ----------------------------
 // Test ctors.
 {
    CDEBUG << "Testing shptr ctors...\n";
    counts.Clear();
    Object* obj = new Object; if ( GetRefCount(*obj) != 0 ) return Fail();
    shptr<Object> sp1 = obj; if ( GetRefCount(*obj) != 1 ) return Fail();
    shptr<Object> sp2 = obj; if ( GetRefCount(*obj) != 2 ) return Fail();
 }

 // ----------------------------
 // Test shptr operator*(), PTR().
 {
    CDEBUG << "Testing operator*(), PTR()...\n";
    counts.Clear();

    const int testVal = 0x11223344;
    shptr<Object> shared( new Object( testVal ) );
    if ( counts.object != 1 ) return Fail();
    if ( (*shared).mTestVal != testVal ) return Fail();

    Object* obj = shared.PTR();
    if ( obj->mTestVal != testVal ) return Fail();

    if ( shared.PTR()->mTestVal != testVal ) return Fail();
 }
 // ----------------------------


 // ----------------------------
 // Test shptr operator=().
 {
    CDEBUG << "Testing operator=()...\n";
    counts.Clear();

    shptr<Object> shared1( new Object( 0x1111 ) );
    shptr<Object> shared2( new Object( 0x2222 ) );

    shared1 = new Object( 0x3333 );  // overwriting destroys
    shared2 = new Object( 0x4444 );

    if ( counts.object != 2 ) return Fail();
    if ( counts.destructor != 2 ) return Fail();
    if ( shared1->mTestVal != 0x3333 ) return Fail();
    if ( shared2->mTestVal != 0x4444 ) return Fail();

    // A new Shared initially has zero refCount.
    // Assign it to a shptr, then to another shptr again.
    // Then refCount should be 2.
   {
    Object* obj = new Object; if ( GetRefCount(*obj) != 0 ) return Fail();
    shptr<Object> sp1;
    shptr<Object> sp2;
    sp1 = obj; if ( GetRefCount(*obj) != 1 ) return Fail();
    sp2 = obj; if ( GetRefCount(*obj) != 2 ) return Fail();
   }
 }
 // more operator=() test cases...
 {
    counts.Clear();

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

    if ( GetRefCount(shared)  != 4 ) return Fail();  // 3 references to one shared object
    if ( GetRefCount(shared2) != 4 ) return Fail();
    if ( GetRefCount(shared3) != 4 ) return Fail();

    if ( shared.PTR()  != obj ) return Fail();
    if ( shared2.PTR() != obj ) return Fail();
    if ( shared2.PTR() != obj ) return Fail();
    if ( shared3.PTR() != obj ) return Fail();

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

    if ( counts.object != 1 ) return Fail();
    if ( counts.constructor != 1 ) return Fail();
    if ( counts.destructor != 0 ) return Fail();
 }
    // shptr auto-vars were deleted, check.
    if ( counts.object != 0 ) return Fail();        // counts should be the same
    if ( counts.destructor != 1 ) return Fail();
 // --
 // yet more operator=() test cases...
 {
    counts.Clear();
    shptr<Object> shared1 = new Object;
    shared1 = new Object;  // overwrite destroys displaced object
    shared1 = new Object;
    if ( counts.constructor != 3 ) return Fail();
    if ( counts.destructor != 2 ) return Fail();
    if ( counts.object != 1 ) return Fail();
 }
    if ( counts.object != 0 ) return Fail();
    if ( counts.destructor != 3 ) return Fail();
 // ----------------------------


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

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

    if ( GetRefCount( shared ) != 3 ) return Fail();      // 3 references to one shared object
    if ( GetRefCount( shared2 ) != 3 ) return Fail();
    if ( GetRefCount( shared3 ) != 3 ) return Fail();

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

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

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

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


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

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

    if ( GetRefCount(shared)  != 2 ) return Fail();   // 2 references to one shared object
    if ( GetRefCount(shared2) != 2 ) return Fail();

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

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

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

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


 // ----------------------------
 // Test shptrs as class members.
 {
    CDEBUG << "Testing shptr as class members...\n";
    counts.Clear();

    // See ctor.
    ClassWith3Shptrs c3;
    if ( c3.mShptr1->mTestVal != 101 ) return Fail();
    if ( c3.mShptr2->mTestVal != 102 ) return Fail();
    if ( c3.mShptr3->mTestVal != 103 ) return Fail();

    // Test copying a class with shptrs.
    Object* obj = new Object;
    ClassWith1Shptr c1a;
    c1a.mShptr1 = obj;  // refCount=1
    ClassWith1Shptr c1b;
    c1b = c1a;              // refCount=2
    if ( c1a.mShptr1.PTR() != obj ) return Fail();
    if ( c1b.mShptr1.PTR() != obj ) return Fail();
    if ( GetRefCount(c1a.mShptr1) != GetRefCount(c1b.mShptr1) ) return Fail();
    if ( GetRefCount(c1a.mShptr1) != 2 ) return Fail();
    if ( GetRefCount(c1b.mShptr1) != 2 ) return Fail();
 }
 // ----------------------------


 // ----------------------------
 // Test shptrs that point to different objects.
 {
    CDEBUG << "Testing shptr to different objects...\n";
    counts.Clear();

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

    if ( GetRefCount(shared)  != 1 ) return Fail();  // 3 references to one shared object
    if ( GetRefCount(shared2) != 1 ) return Fail();

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


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

    shptr<Object> shared( new Object );  // refcount = 1
    if ( GetRefCount(shared) != 1 ) return Fail();
    if ( counts.object != 1 ) return Fail();
    if ( counts.constructor != 1 ) return Fail();
    if ( counts.destructor != 0 ) return Fail();

    // Pass shptr to a function by value
    // which should increment the reference count.
    // Note shptr isn't meant to be passed as a reference nor pointer.
    if ( not FuncPassedShptr( shared, 1, 1 ) ) return Fail();
    if ( GetRefCount(shared) != 1 ) return Fail();   // call func didn't keep its refcount
    if ( counts.object != 1 ) return Fail();
    if ( counts.constructor != 1 ) return Fail();
    if ( counts.destructor != 0 ) return Fail();
   }
    if ( counts.object != 0 ) return Fail();
    if ( counts.constructor != 1 ) return Fail();
    if ( counts.destructor != 1 ) return Fail();
 }
 // ----------------------------
 {  // Function that is called keeps the shptr.
   {
    counts.Clear();
    shptr<Object> shared( new Object );  // refcount = 1
    if ( not FuncPassedShptrThatKeepsIt( shared ) ) return Fail();
    if ( GetRefCount(shared) != 2 ) return Fail();
    if ( counts.object != 1 ) return Fail();
    if ( counts.constructor != 1 ) return Fail();
    if ( counts.destructor != 0 ) return Fail();
   }
    // Object should still exist even though the autovar shptr was destroyed.
    if ( counts.object != 1 ) return Fail();
    if ( counts.constructor != 1 ) return Fail();
    if ( counts.destructor != 0 ) return Fail();
 }

 // ----------------------------
 // Test function that returns a shptr
 {
   {
    CDEBUG << "Testing function that returns a shptr...\n";
    counts.Clear();
    shptr<Object> shared = FuncThatReturnsShptr();
    if ( GetRefCount(shared) != 1 ) return Fail();
    if ( counts.object != 1 ) return Fail();

    if ( shared.PTR() == NULL ) return Fail();
    if ( counts.object != 1 ) return Fail();  // PTR() should have no affect on refCount
   }
    if ( counts.object != 0 ) return Fail();  // it should've been destroyed
 }

 {
   {
    counts.Clear();
    shptr<Object> shared = FuncThatReturnsShptr2();
    if ( GetRefCount(shared) != 1 ) return Fail();
    if ( counts.object != 1 ) return Fail();
   }
    if ( counts.object != 0 ) return Fail();  // it should've been destroyed
 }

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

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

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

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

    Object* obj2 = new Object;
    shared2 = obj2;

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

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

    if ( GetRefCount(shared2) != 1 ) return Fail();

    // The other 3 shptr should be affected,
    // and have refCount = 3.

    if ( GetRefCount(shared)  != 3 ) return Fail();
    if ( GetRefCount(shared3) != 3 ) return Fail();
    if ( GetRefCount(shared4) != 3 ) return Fail();

    // refCount pointer of shared2 should differ.
    if ( GetRefCount(shared2) == GetRefCount(shared) ) return Fail();
    if ( GetRefCount(shared2) == GetRefCount(shared3) ) return Fail();
    if ( GetRefCount(shared2) == GetRefCount(shared4) ) return Fail();
 }
 // ----------------------------


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

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

    if ( GetRefCount(shared)  != 3 ) return Fail();   // 3 references to one shared object
    if ( GetRefCount(shared2) != 3 ) return Fail();
    if ( GetRefCount(shared3) != 3 ) return Fail();
 }
 // ----------------------------


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

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

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

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

        if ( vec[i] != shared ) return Fail();
        if ( vec[i].CONST_PTR() != shared.CONST_PTR() ) return Fail();

        if ( GetRefCount(shared) != i+2 ) return Fail();  // when i=0, refCount will be 2
        if ( GetRefCount(vec[i]) != i+2 ) return Fail();

        if ( vec[i]->Magic() != MAGIC ) return Fail();
    }
    if ( GetRefCount(shared) != count+1 ) return Fail();        // +1 for original "shared"

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


 // ----------------------------
 // Test using STL vector and push_back().
 {
    const int count = 50;
   {
    CDEBUG << "Testing STL vector with push_back()...\n";
    counts.Clear();

    std::vector<shptr<Object> > vec;

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

    int expectedRefCount = 1;

    for ( int i = 0; i < count; ++i )
    {
        int prevRefCount = GetRefCount(shared);

        vec.push_back( shared );
        ++expectedRefCount;

        if ( vec[vec.size()-1] != shared ) return Fail();
        if ( vec[vec.size()-1]->Magic() != MAGIC ) return Fail();

        // After push_back(), refCount should've increased.
        if ( GetRefCount(shared) <= prevRefCount ) return Fail();  // refCount should've inc'd
        if ( GetRefCount(vec[vec.size()-1]) != GetRefCount(shared) ) return Fail();

        // refCount should increase by one every time.
        if ( GetRefCount(vec[vec.size()-1]) != expectedRefCount ) return Fail();
        if ( GetRefCount(shared)            != expectedRefCount ) return Fail();

        if ( counts.object != 1 ) return Fail();  // there's one object
    }
   }
    if ( counts.object != 0 ) return Fail();  // it should've been deleted
    if ( counts.constructor != 1 ) return Fail();  // should've been only one
    if ( counts.destructor != 1 ) return Fail();
}
 // ----------------------------


 // ----------------------------
 // Test using STL vector and push_back() (another case).
 {
    const int count = 50;
   {
    CDEBUG << "Testing STL vector with push_back( shptr<Object>( new Object ) )...\n";
    counts.Clear();

    std::vector<shptr<Object> > vec;

    for ( int i = 0; i < count; ++i )
    {
        int prevObjectCount = counts.object;

        vec.push_back( shptr<Object>( new Object ) );

        if ( counts.object != prevObjectCount+1 ) return Fail();
        if ( GetRefCount(vec[vec.size()-1]) != 1 ) return Fail();  // pushed value makes 1 ref
        if ( vec[vec.size()-1]->Magic() != MAGIC ) return Fail();
    }
   }
    if ( counts.object != 0 ) return Fail();  // all should've been deleted
    if ( counts.constructor != count ) return Fail();  // should've been count instances
    if ( counts.destructor != count ) return Fail();
}
 // ----------------------------


 // ----------------------------
 // Test using a huge STL vector.
 {
    CDEBUG << "Testing huge STL vector<shptr> to induce reallocation...\n";
    counts.Clear();
    const int count = 500*1000;  // induce STL vector to reallocate
    shptr<Object> sharedObj = new Object;
  {
    std::vector<shptr<Object> > vec;
    vec.reserve( 10 );  // induce STL vector to reallocate
    void* first = NULL;
    for ( int i = 0; i < count; ++i )
    {
        vec.push_back( sharedObj  );
        if ( i == 0 ) first = (void*) &vec[0];
    }
    // There's one shared object and it has count+1 reference counts.
    if ( GetRefCount(sharedObj) != count + 1 ) return Fail();
    if ( counts.constructor != 1 ) return Fail();
    if ( counts.object      != 1 ) return Fail();

    // STL should have reallocated the vector, else this test case was ineffective.
    if ( (void*)&vec[0] == first ) return Fail();
  }
    // STL vector was destroyed.  One refcount remains at "sharedObj".
    if ( GetRefCount(sharedObj) != 1 ) return Fail();
    if ( counts.object != 1 ) return Fail();
 }
    // Now last shared object was destroyed.
    if ( counts.constructor != 1 ) return Fail();
    if ( counts.destructor != 1 ) return Fail();
    if ( counts.object != 0 ) return Fail();

 // ----------------------------


 // ----------------------------
 // Test finding/removing a shptr in a STL list.
 {
    CDEBUG << "Testing STL list<shptr>...\n";

    Int::Clear();
    std::list<shptr<Int> > nums;

    shptr<Int> one   = new Int(1);
    shptr<Int> two   = new Int(2);
    shptr<Int> three = new Int(3);
    if ( Int::msInstanceCount != 3 ) { Fail(); }
    if ( one.GetRefCount() != 1 ) { Fail(); }
    if ( two.GetRefCount() != 1 ) { Fail(); }
    if ( three.GetRefCount() != 1 ) { Fail(); }

    nums.push_front( one );
    nums.push_front( two );
    nums.push_front( three );
    if ( Int::msInstanceCount != 3 ) { Fail(); }
    if ( one.GetRefCount() != 2 ) { Fail(); }
    if ( two.GetRefCount() != 2 ) { Fail(); }
    if ( three.GetRefCount() != 2 ) { Fail(); }

    // Erase two.
    std::list<shptr<Int> >::iterator iter = std::find( nums.begin(), nums.end(), two );
    if ( iter == nums.end() ) { return Fail(); }
    if ( (*iter)->Get() != 2 ) { return Fail(); }
    nums.erase( iter );
    if ( one.GetRefCount() != 2 ) { Fail(); }
    if ( two.GetRefCount() != 1 ) { Fail(); }  // erased two, minus one ref
    if ( three.GetRefCount() != 2 ) { Fail(); }
 }
    if ( Int::msInstanceCount != 0 ) { Fail(); }
 // ----------------------------


 // ----------------------------
 // Test self-assignment.
 {
    CDEBUG << "Testing self-assignment...\n";
    counts.Clear();
  {
    shptr<Object> obj( new Object );
    obj = obj;
    if ( GetRefCount(obj) != 1 ) return Fail();
  }

  // Test operator=(Shared*) ability to handle a form of self-assignment.
  {
    Object* obj = new Object;
    shptr<Object> shptrObj;
    shptrObj = obj;
    shptrObj = obj; if ( shptrObj.mObj == NULL ) return Fail();  // test if deleted
    if ( GetRefCount(*obj) != 1 ) return Fail();
    shptrObj = obj;  // self-assignment doesn't effectively change refCount,
    shptrObj = obj;  // though shptr::operator=() increments and decrements it
    shptrObj = obj;
    if ( GetRefCount(*obj) != 1 ) return Fail();
  }
 }
 // ----------------------------

 // ----------------------------
 // Test converting a raw pointer to Shared back to a shptr.
 {
    CDEBUG << "Testing converting back to a shptr...\n";
    counts.Clear();
  {
    shptr<Object> obj( new Object );
    obj = obj;

    Object* raw = obj.PTR();
    shptr<Object> obj2 = raw;

    if ( GetRefCount(obj) != 2 ) return Fail();
  }
 }
 // ----------------------------

 // ============
    return true;
}

/*****************************************************************************
 * Test that a Shared object can be interchanged with different kinds of shptr.
 * This uses shptr0 but (intentionally doesn't test its ability to handle NULL cases
 * (it has a separate test).
 *****************************************************************************/
bool
TestInterchangingShptr( void )
{
 {
    CDEBUG << "Testing interchanging Shared...\n";
    counts.Clear();

    shptr<Object> obj = new Object;
    if ( counts.object != 1 ) return Fail();
    if ( counts.constructor != 1 ) return Fail();
    if ( obj.GetRefCount() != 1 ) return Fail();

    shptr0<Object> obj0 = obj.PTR();
    if ( counts.object != 1 ) return Fail();
    if ( counts.constructor != 1 ) return Fail();
    if ( obj.GetRefCount() != 2 ) return Fail();
 }
    return true;
}

/*****************************************************************************
 * Test copying Shared.
 *****************************************************************************/
class DerivedShared : public Shared
{
};

bool
TestCopyingShared( void )
{
    CDEBUG << "Testing copying Shared...\n";
    shptr<DerivedShared> obj1 = new DerivedShared;  // refcount=1
    DerivedShared        obj2;                      // refcount=0

    // Will obj1's refcount be overwritten with 0?  It should be 1.
    *obj1 = obj2;  // copy Shared = Shared
    if ( obj1->GetRefCount() != 1 ) { Fail(); }
    if ( obj2.GetRefCount() != 0 ) { Fail(); }
    return true;
}

/*****************************************************************************
 * Test shptr polymorphism: ability to pass Shared<Derived> as Shared<Base>.
 *****************************************************************************/
class Base_Shared : public Shared  // running out of names
{
public:
    CLASS_CONST uint BASE_SIG = 0x1234aaaa;
    Base_Shared( void ) : mBaseSig(BASE_SIG) { }
    uint mBaseSig;
};

class Derived_Shared : public Base_Shared
{
public:
    CLASS_CONST uint DERIVED_SIG = 0x99887766;
    Derived_Shared( void ) : mDerivedSig(DERIVED_SIG) { }
    uint mDerivedSig;
};

shptr<Base_Shared>
ReflectBaseShared( shptr<Base_Shared> base )
{
    if ( base->mBaseSig != Base_Shared::BASE_SIG ) { Fail(); }
    if ( base->GetRefCount() != 2 ) { Fail(); return NULL; }
    return base;
}

bool
TestBaseDerivedShptr( void )
{
 {
    CDEBUG << "Testing passing Shared<Derived> as Shared<Base>...\n";
    shptr<Derived_Shared> derived = new Derived_Shared;  // refcount=1

    if ( derived->GetRefCount() != 1 ) { return Fail(); }
    shptr<Base_Shared> base = ReflectBaseShared( derived );
    if ( derived->GetRefCount() != 2 ) { return Fail(); }  // assignment to base incs it
    if (    base->GetRefCount() != 2 ) { return Fail(); }  // base=derived (same object)
    Derived_Shared* derivedRaw = static_cast<Derived_Shared*>( base.PTR() );
    if ( derivedRaw->mDerivedSig != Derived_Shared::DERIVED_SIG ) { return Fail(); }
 }

#if 0 // This is of course illegal and should not, and does not, compile (gcc 4.1).
 {
    shptr<Base_Shared>    base( new Base_Shared );
    shptr<Derived_Shared> derived( base );
 }
#endif

    return true;
}

/*****************************************************************************
 * main.
 *****************************************************************************/
int
main( int argc, char** argv )
{
    RUN_TEST( "shptr2", TEST_ERROR_MODE_ABORT,
              TestShptr()
          and TestInterchangingShptr()
          and TestCopyingShared()
          and TestBaseDerivedShptr() )
}
