phoenix-firestorm/indra/llcommon/tests/llpounceable_test.cpp

231 lines
8.5 KiB
C++

/**
* @file llpounceable_test.cpp
* @author Nat Goodspeed
* @date 2015-05-22
* @brief Test for llpounceable.
*
* $LicenseInfo:firstyear=2015&license=viewerlgpl$
* Copyright (c) 2015, Linden Research, Inc.
* $/LicenseInfo$
*/
// Precompiled header
#include "linden_common.h"
// associated header
#include "llpounceable.h"
// STL headers
// std headers
// external library headers
#include <boost/bind.hpp>
// other Linden headers
#include "../test/lltut.h"
/*----------------------------- string testing -----------------------------*/
void append(std::string* dest, const std::string& src)
{
dest->append(src);
}
/*-------------------------- Data-struct testing ---------------------------*/
struct Data
{
Data(const std::string& data):
mData(data)
{}
const std::string mData;
};
void setter(Data** dest, Data* ptr)
{
*dest = ptr;
}
static Data* static_check = 0;
// Set up an extern pointer to an LLPounceableStatic so the linker will fill
// in the forward reference from below, before runtime.
extern LLPounceable<Data*, LLPounceableStatic> gForward;
struct EnqueueCall
{
EnqueueCall()
{
// Intentionally use a forward reference to an LLPounceableStatic that
// we believe is NOT YET CONSTRUCTED. This models the scenario in
// which a constructor in another translation unit runs before
// constructors in this one. We very specifically want callWhenReady()
// to work even in that case: we need the LLPounceableQueueImpl to be
// initialized even if the LLPounceable itself is not.
gForward.callWhenReady(boost::bind(setter, &static_check, _1));
}
} nqcall;
// When this declaration is processed, we should enqueue the
// setter(&static_check, _1) call for when gForward is set non-NULL. Needless
// to remark, we want this call not to crash.
// Now declare gForward. Its constructor should not run until after nqcall's.
LLPounceable<Data*, LLPounceableStatic> gForward;
/*****************************************************************************
* TUT
*****************************************************************************/
namespace tut
{
struct llpounceable_data
{
};
typedef test_group<llpounceable_data> llpounceable_group;
typedef llpounceable_group::object object;
llpounceable_group llpounceablegrp("llpounceable");
template<> template<>
void object::test<1>()
{
set_test_name("LLPounceableStatic out-of-order test");
// LLPounceable<T, LLPounceableStatic>::callWhenReady() must work even
// before LLPounceable's constructor runs. That's the whole point of
// implementing it with an LLSingleton queue. This models (say)
// LLPounceableStatic<LLMessageSystem*, LLPounceableStatic>.
ensure("static_check should still be null", ! static_check);
Data myData("test<1>");
gForward = &myData; // should run setter
ensure_equals("static_check should be &myData", static_check, &myData);
}
template<> template<>
void object::test<2>()
{
set_test_name("LLPounceableQueue different queues");
// We expect that LLPounceable<T, LLPounceableQueue> should have
// different queues because that specialization stores the queue
// directly in the LLPounceable instance.
Data *aptr = 0, *bptr = 0;
LLPounceable<Data*> a, b;
a.callWhenReady(boost::bind(setter, &aptr, _1));
b.callWhenReady(boost::bind(setter, &bptr, _1));
ensure("aptr should be null", ! aptr);
ensure("bptr should be null", ! bptr);
Data adata("a"), bdata("b");
a = &adata;
ensure_equals("aptr should be &adata", aptr, &adata);
// but we haven't yet set b
ensure("bptr should still be null", !bptr);
b = &bdata;
ensure_equals("bptr should be &bdata", bptr, &bdata);
}
template<> template<>
void object::test<3>()
{
set_test_name("LLPounceableStatic different queues");
// LLPounceable<T, LLPounceableStatic> should also have a distinct
// queue for each instance, but that engages an additional map lookup
// because there's only one LLSingleton for each T.
Data *aptr = 0, *bptr = 0;
LLPounceable<Data*, LLPounceableStatic> a, b;
a.callWhenReady(boost::bind(setter, &aptr, _1));
b.callWhenReady(boost::bind(setter, &bptr, _1));
ensure("aptr should be null", ! aptr);
ensure("bptr should be null", ! bptr);
Data adata("a"), bdata("b");
a = &adata;
ensure_equals("aptr should be &adata", aptr, &adata);
// but we haven't yet set b
ensure("bptr should still be null", !bptr);
b = &bdata;
ensure_equals("bptr should be &bdata", bptr, &bdata);
}
template<> template<>
void object::test<4>()
{
set_test_name("LLPounceable<T> looks like T");
// We want LLPounceable<T, TAG> to be drop-in replaceable for a plain
// T for read constructs. In particular, it should behave like a dumb
// pointer -- and with zero abstraction cost for such usage.
Data* aptr = 0;
Data a("a");
// should be able to initialize a pounceable (when its constructor
// runs)
LLPounceable<Data*> pounceable(&a);
// should be able to pass LLPounceable<T> to function accepting T
setter(&aptr, pounceable);
ensure_equals("aptr should be &a", aptr, &a);
// should be able to dereference with *
ensure_equals("deref with *", (*pounceable).mData, "a");
// should be able to dereference with ->
ensure_equals("deref with ->", pounceable->mData, "a");
// bool operations
ensure("test with operator bool()", pounceable);
ensure("test with operator !()", ! (! pounceable));
}
template<> template<>
void object::test<5>()
{
set_test_name("Multiple callWhenReady() queue items");
Data *p1 = 0, *p2 = 0, *p3 = 0;
Data a("a");
LLPounceable<Data*> pounceable;
// queue up a couple setter() calls for later
pounceable.callWhenReady(boost::bind(setter, &p1, _1));
pounceable.callWhenReady(boost::bind(setter, &p2, _1));
// should still be pending
ensure("p1 should be null", !p1);
ensure("p2 should be null", !p2);
ensure("p3 should be null", !p3);
pounceable = 0;
// assigning a new empty value shouldn't flush the queue
ensure("p1 should still be null", !p1);
ensure("p2 should still be null", !p2);
ensure("p3 should still be null", !p3);
// using whichever syntax
pounceable.reset(0);
// try to make ensure messages distinct... tough to pin down which
// ensure() failed if multiple ensure() calls in the same test<n> have
// the same message!
ensure("p1 should again be null", !p1);
ensure("p2 should again be null", !p2);
ensure("p3 should again be null", !p3);
pounceable.reset(&a); // should flush queue
ensure_equals("p1 should be &a", p1, &a);
ensure_equals("p2 should be &a", p2, &a);
ensure("p3 still not set", !p3);
// immediate call
pounceable.callWhenReady(boost::bind(setter, &p3, _1));
ensure_equals("p3 should be &a", p3, &a);
}
template<> template<>
void object::test<6>()
{
set_test_name("queue order");
std::string data;
LLPounceable<std::string*> pounceable;
pounceable.callWhenReady(boost::bind(append, _1, "a"));
pounceable.callWhenReady(boost::bind(append, _1, "b"));
pounceable.callWhenReady(boost::bind(append, _1, "c"));
pounceable = &data;
ensure_equals("callWhenReady() must preserve chronological order",
data, "abc");
std::string data2;
pounceable = NULL;
pounceable.callWhenReady(boost::bind(append, _1, "d"));
pounceable.callWhenReady(boost::bind(append, _1, "e"));
pounceable.callWhenReady(boost::bind(append, _1, "f"));
pounceable = &data2;
ensure_equals("LLPounceable must reset queue when fired",
data2, "def");
}
template<> template<>
void object::test<7>()
{
set_test_name("compile-fail test, uncomment to check");
// The following declaration should fail: only LLPounceableQueue and
// LLPounceableStatic should work as tags.
// LLPounceable<Data*, int> pounceable;
}
} // namespace tut