diff options
Diffstat (limited to 'indra/llcommon/tests')
-rw-r--r-- | indra/llcommon/tests/bitpack_test.cpp | 125 | ||||
-rw-r--r-- | indra/llcommon/tests/listener.h | 139 | ||||
-rw-r--r-- | indra/llcommon/tests/llbase64_test.cpp | 83 | ||||
-rw-r--r-- | indra/llcommon/tests/lldate_test.cpp | 2 | ||||
-rw-r--r-- | indra/llcommon/tests/lldependencies_test.cpp | 275 | ||||
-rw-r--r-- | indra/llcommon/tests/llerror_test.cpp | 768 | ||||
-rw-r--r-- | indra/llcommon/tests/lleventcoro_test.cpp | 782 | ||||
-rw-r--r-- | indra/llcommon/tests/lleventfilter_test.cpp | 276 | ||||
-rw-r--r-- | indra/llcommon/tests/llframetimer_test.cpp | 118 | ||||
-rw-r--r-- | indra/llcommon/tests/llrand_test.cpp | 2 | ||||
-rw-r--r-- | indra/llcommon/tests/llsdserialize_test.cpp | 76 | ||||
-rw-r--r-- | indra/llcommon/tests/llstring_test.cpp | 1 | ||||
-rw-r--r-- | indra/llcommon/tests/lltreeiterators_test.cpp | 1222 | ||||
-rw-r--r-- | indra/llcommon/tests/lluri_test.cpp | 370 | ||||
-rw-r--r-- | indra/llcommon/tests/reflection_test.cpp | 226 | ||||
-rw-r--r-- | indra/llcommon/tests/stringize_test.cpp | 110 | ||||
-rw-r--r-- | indra/llcommon/tests/wrapllerrs.h | 56 |
17 files changed, 4627 insertions, 4 deletions
diff --git a/indra/llcommon/tests/bitpack_test.cpp b/indra/llcommon/tests/bitpack_test.cpp new file mode 100644 index 0000000000..09fd037f02 --- /dev/null +++ b/indra/llcommon/tests/bitpack_test.cpp @@ -0,0 +1,125 @@ +/** + * @file bitpack_test.cpp + * @author Adroit + * @date 2007-02 + * @brief llstreamtools test cases. + * + * $LicenseInfo:firstyear=2007&license=viewergpl$ + * + * Copyright (c) 2007-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "../bitpack.h" + +#include "../test/lltut.h" + + +namespace tut +{ + struct bit_pack + { + }; + typedef test_group<bit_pack> bit_pack_t; + typedef bit_pack_t::object bit_pack_object_t; + tut::bit_pack_t tut_bit_pack("bitpack"); + + // pack -> unpack + template<> template<> + void bit_pack_object_t::test<1>() + { + U8 packbuffer[255]; + U8 unpackbuffer[255]; + int pack_bufsize = 0; + int unpack_bufsize = 0; + + LLBitPack bitpack(packbuffer, 255); + + char str[] = "SecondLife is a 3D virtual world"; + int len = sizeof(str); + pack_bufsize = bitpack.bitPack((U8*) str, len*8); + pack_bufsize = bitpack.flushBitPack(); + + LLBitPack bitunpack(packbuffer, pack_bufsize*8); + unpack_bufsize = bitunpack.bitUnpack(unpackbuffer, len*8); + ensure("bitPack: unpack size should be same as string size prior to pack", len == unpack_bufsize); + ensure_memory_matches("str->bitPack->bitUnpack should be equal to string", str, len, unpackbuffer, unpack_bufsize); + } + + // pack large, unpack in individual bytes + template<> template<> + void bit_pack_object_t::test<2>() + { + U8 packbuffer[255]; + U8 unpackbuffer[255]; + int pack_bufsize = 0; + int unpack_bufsize = 0; + + LLBitPack bitpack(packbuffer, 255); + + char str[] = "SecondLife"; + int len = sizeof(str); + pack_bufsize = bitpack.bitPack((U8*) str, len*8); + pack_bufsize = bitpack.flushBitPack(); + + LLBitPack bitunpack(packbuffer, pack_bufsize*8); + unpack_bufsize = bitunpack.bitUnpack(&unpackbuffer[0], 8); + ensure("bitPack: individual unpack: 0", unpackbuffer[0] == (U8) str[0]); + unpack_bufsize = bitunpack.bitUnpack(&unpackbuffer[0], 8); + ensure("bitPack: individual unpack: 1", unpackbuffer[0] == (U8) str[1]); + unpack_bufsize = bitunpack.bitUnpack(&unpackbuffer[0], 8); + ensure("bitPack: individual unpack: 2", unpackbuffer[0] == (U8) str[2]); + unpack_bufsize = bitunpack.bitUnpack(&unpackbuffer[0], 8); + ensure("bitPack: individual unpack: 3", unpackbuffer[0] == (U8) str[3]); + unpack_bufsize = bitunpack.bitUnpack(&unpackbuffer[0], 8); + ensure("bitPack: individual unpack: 4", unpackbuffer[0] == (U8) str[4]); + unpack_bufsize = bitunpack.bitUnpack(&unpackbuffer[0], 8); + ensure("bitPack: individual unpack: 5", unpackbuffer[0] == (U8) str[5]); + unpack_bufsize = bitunpack.bitUnpack(unpackbuffer, 8*4); // Life + ensure_memory_matches("bitPack: 4 bytes unpack:", unpackbuffer, 4, str+6, 4); + } + + // U32 packing + template<> template<> + void bit_pack_object_t::test<3>() + { + U8 packbuffer[255]; + int pack_bufsize = 0; + + LLBitPack bitpack(packbuffer, 255); + U32 num = 0x41fab67a; + pack_bufsize = bitpack.bitPack((U8*)&num, 8*sizeof(U32)); + pack_bufsize = bitpack.flushBitPack(); + + LLBitPack bitunpack(packbuffer, pack_bufsize*8); + U32 res = 0; + // since packing and unpacking is done on same machine in the unit test run, + // endianness should not matter + bitunpack.bitUnpack((U8*) &res, sizeof(res)*8); + ensure("U32->bitPack->bitUnpack->U32 should be equal", num == res); + } +} diff --git a/indra/llcommon/tests/listener.h b/indra/llcommon/tests/listener.h new file mode 100644 index 0000000000..fa12f944ef --- /dev/null +++ b/indra/llcommon/tests/listener.h @@ -0,0 +1,139 @@ +/** + * @file listener.h + * @author Nat Goodspeed + * @date 2009-03-06 + * @brief Useful for tests of the LLEventPump family of classes + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LISTENER_H) +#define LL_LISTENER_H + +#include "llsd.h" +#include <iostream> + +/***************************************************************************** +* test listener class +*****************************************************************************/ +class Listener; +std::ostream& operator<<(std::ostream&, const Listener&); + +/// Bear in mind that this is strictly for testing +class Listener +{ +public: + /// Every Listener is instantiated with a name + Listener(const std::string& name): + mName(name) + { +// std::cout << *this << ": ctor\n"; + } +/*==========================================================================*| + // These methods are only useful when trying to track Listener instance + // lifespan + Listener(const Listener& that): + mName(that.mName), + mLastEvent(that.mLastEvent) + { + std::cout << *this << ": copy\n"; + } + virtual ~Listener() + { + std::cout << *this << ": dtor\n"; + } +|*==========================================================================*/ + /// You can request the name + std::string getName() const { return mName; } + /// This is a typical listener method that returns 'false' when done, + /// allowing subsequent listeners on the LLEventPump to process the + /// incoming event. + bool call(const LLSD& event) + { +// std::cout << *this << "::call(" << event << ")\n"; + mLastEvent = event; + return false; + } + /// This is an alternate listener that returns 'true' when done, which + /// stops processing of the incoming event. + bool callstop(const LLSD& event) + { +// std::cout << *this << "::callstop(" << event << ")\n"; + mLastEvent = event; + return true; + } + /// ListenMethod can represent either call() or callstop(). + typedef bool (Listener::*ListenMethod)(const LLSD&); + /** + * This helper method is only because our test code makes so many + * repetitive listen() calls to ListenerMethods. In real code, you should + * call LLEventPump::listen() directly so it can examine the specific + * object you pass to boost::bind(). + */ + LLBoundListener listenTo(LLEventPump& pump, + ListenMethod method=&Listener::call, + const LLEventPump::NameList& after=LLEventPump::empty, + const LLEventPump::NameList& before=LLEventPump::empty) + { + return pump.listen(getName(), boost::bind(method, this, _1), after, before); + } + /// Both call() and callstop() set mLastEvent. Retrieve it. + LLSD getLastEvent() const + { +// std::cout << *this << "::getLastEvent() -> " << mLastEvent << "\n"; + return mLastEvent; + } + /// Reset mLastEvent to a known state. + void reset(const LLSD& to = LLSD()) + { +// std::cout << *this << "::reset(" << to << ")\n"; + mLastEvent = to; + } + +private: + std::string mName; + LLSD mLastEvent; +}; + +std::ostream& operator<<(std::ostream& out, const Listener& listener) +{ + out << "Listener(" << listener.getName() /* << "@" << &listener */ << ')'; + return out; +} + +/** + * This class tests the relative order in which various listeners on a given + * LLEventPump are called. Each listen() call binds a particular string, which + * we collect for later examination. The actual event is ignored. + */ +struct Collect +{ + bool add(const std::string& bound, const LLSD& event) + { + result.push_back(bound); + return false; + } + void clear() { result.clear(); } + typedef std::vector<std::string> StringList; + StringList result; +}; + +std::ostream& operator<<(std::ostream& out, const Collect::StringList& strings) +{ + out << '('; + Collect::StringList::const_iterator begin(strings.begin()), end(strings.end()); + if (begin != end) + { + out << '"' << *begin << '"'; + while (++begin != end) + { + out << ", \"" << *begin << '"'; + } + } + out << ')'; + return out; +} + +#endif /* ! defined(LL_LISTENER_H) */ diff --git a/indra/llcommon/tests/llbase64_test.cpp b/indra/llcommon/tests/llbase64_test.cpp new file mode 100644 index 0000000000..6009788b22 --- /dev/null +++ b/indra/llcommon/tests/llbase64_test.cpp @@ -0,0 +1,83 @@ +/** + * @file llbase64_test.cpp + * @author James Cook + * @date 2007-02-04 + * + * $LicenseInfo:firstyear=2007&license=viewergpl$ + * + * Copyright (c) 2007-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include <string> + +#include "linden_common.h" + +#include "../llbase64.h" +#include "../lluuid.h" + +#include "../test/lltut.h" + +namespace tut +{ + struct base64_data + { + }; + typedef test_group<base64_data> base64_test; + typedef base64_test::object base64_object; + tut::base64_test base64("base64"); + + template<> template<> + void base64_object::test<1>() + { + std::string result; + + result = LLBase64::encode(NULL, 0); + ensure("encode nothing", (result == "") ); + + LLUUID nothing; + result = LLBase64::encode(¬hing.mData[0], UUID_BYTES); + ensure("encode blank uuid", + (result == "AAAAAAAAAAAAAAAAAAAAAA==") ); + + LLUUID id("526a1e07-a19d-baed-84c4-ff08a488d15e"); + result = LLBase64::encode(&id.mData[0], UUID_BYTES); + ensure("encode random uuid", + (result == "UmoeB6Gduu2ExP8IpIjRXg==") ); + + } + + template<> template<> + void base64_object::test<2>() + { + std::string result; + + U8 blob[40] = { 115, 223, 172, 255, 140, 70, 49, 125, 236, 155, 45, 199, 101, 17, 164, 131, 230, 19, 80, 64, 112, 53, 135, 98, 237, 12, 26, 72, 126, 14, 145, 143, 118, 196, 11, 177, 132, 169, 195, 134 }; + result = LLBase64::encode(&blob[0], 40); + ensure("encode 40 bytes", + (result == "c9+s/4xGMX3smy3HZRGkg+YTUEBwNYdi7QwaSH4OkY92xAuxhKnDhg==") ); + } + +} diff --git a/indra/llcommon/tests/lldate_test.cpp b/indra/llcommon/tests/lldate_test.cpp index 1e36fdd119..c31259227a 100644 --- a/indra/llcommon/tests/lldate_test.cpp +++ b/indra/llcommon/tests/lldate_test.cpp @@ -1,5 +1,5 @@ /** - * @file lldate_tut.cpp + * @file lldate_test.cpp * @author Adroit * @date 2007-02 * @brief LLDate test cases. diff --git a/indra/llcommon/tests/lldependencies_test.cpp b/indra/llcommon/tests/lldependencies_test.cpp new file mode 100644 index 0000000000..f3c25de8b5 --- /dev/null +++ b/indra/llcommon/tests/lldependencies_test.cpp @@ -0,0 +1,275 @@ +/** + * @file lldependencies_tut.cpp + * @author Nat Goodspeed + * @date 2008-09-17 + * @brief Test of lldependencies.h + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * Copyright (c) 2008, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// STL headers +#include <iostream> +#include <string> +// std headers +// external library headers +#include <boost/assign/list_of.hpp> +// Precompiled header +#include "linden_common.h" +// associated header +#include "../lldependencies.h" +// other Linden headers +#include "../test/lltut.h" + +using boost::assign::list_of; + +#if LL_WINDOWS +#pragma warning (disable : 4675) // "resolved by ADL" -- just as I want! +#endif + +typedef LLDependencies<> StringDeps; +typedef StringDeps::KeyList StringList; + +// We use the very cool boost::assign::list_of() construct to specify vectors +// of strings inline. For reasons on which I'm not entirely clear, though, it +// needs a helper function. You can use list_of() to construct an implicit +// StringList (std::vector<std::string>) by conversion, e.g. for a function +// parameter -- but if you simply write StringList(list_of("etc.")), you get +// ambiguity errors. Shrug! +template<typename CONTAINER> +CONTAINER make(const CONTAINER& data) +{ + return data; +} + +// Display an arbitary value as itself... +template<typename T> +std::ostream& display(std::ostream& out, const T& value) +{ + out << value; + return out; +} + +// ...but display std::string enclosed in double quotes. +template<> +std::ostream& display(std::ostream& out, const std::string& value) +{ + out << '"' << value << '"'; + return out; +} + +// display any sequence compatible with Boost.Range +template<typename SEQUENCE> +std::ostream& display_seq(std::ostream& out, + const std::string& open, const SEQUENCE& seq, const std::string& close) +{ + out << open; + typename boost::range_const_iterator<SEQUENCE>::type + sli = boost::begin(seq), + slend = boost::end(seq); + if (sli != slend) + { + display(out, *sli); + while (++sli != slend) + { + out << ", "; + display(out, *sli); + } + } + out << close; + return out; +} + +// helper to dump a StringList to std::cout if needed +template<typename ENTRY> +std::ostream& operator<<(std::ostream& out, const std::vector<ENTRY>& list) +{ + display_seq(out, "(", list, ")"); + return out; +} + +template<typename ENTRY> +std::ostream& operator<<(std::ostream& out, const std::set<ENTRY>& set) +{ + display_seq(out, "{", set, "}"); + return out; +} + +const std::string& extract_key(const LLDependencies<>::value_type& entry) +{ + return entry.first; +} + +// helper to return a StringList of keys from LLDependencies::sort() +StringList sorted_keys(LLDependencies<>& deps) +{ + // 1. Call deps.sort(), returning a value_type range of (key, node) pairs. + // 2. Use make_transform_range() to obtain a range of just keys. + // 3. Use instance_from_range to instantiate a StringList from that range. + // 4. Return by value "slices" instance_from_range<StringList> (a subclass + // of StringList) to its base class StringList. + return instance_from_range<StringList>(make_transform_range(deps.sort(), extract_key)); +} + +template<typename RANGE> +bool is_empty(const RANGE& range) +{ + return boost::begin(range) == boost::end(range); +} + +/***************************************************************************** +* tut test group +*****************************************************************************/ +namespace tut +{ + struct deps_data + { + }; + typedef test_group<deps_data> deps_group; + typedef deps_group::object deps_object; + tut::deps_group depsgr("lldependencies"); + + template<> template<> + void deps_object::test<1>() + { + StringDeps deps; + StringList empty; + // The quick brown fox jumps over the lazy yellow dog. + // (note, "The" and "the" are distinct, else this test wouldn't work) + deps.add("lazy"); + ensure_equals(sorted_keys(deps), make<StringList>(list_of("lazy"))); + deps.add("jumps"); + ensure("found lazy", deps.get("lazy")); + ensure("not found dog.", ! deps.get("dog.")); + // NOTE: Maybe it's overkill to test each of these intermediate + // results before all the interdependencies have been specified. My + // thought is simply that if the order changes, I'd like to know why. + // A change to the implementation of boost::topological_sort() would + // be an acceptable reason, and you can simply update the expected + // test output. + ensure_equals(sorted_keys(deps), make<StringList>(list_of("lazy")("jumps"))); + deps.add("The", 0, empty, list_of("fox")("dog.")); + // Test key accessors + ensure("empty before deps for missing key", is_empty(deps.get_before_range("bogus"))); + ensure("empty before deps for jumps", is_empty(deps.get_before_range("jumps"))); + ensure_equals(instance_from_range< std::set<std::string> >(deps.get_before_range("The")), + make< std::set<std::string> >(list_of("dog.")("fox"))); + // resume building dependencies + ensure_equals(sorted_keys(deps), make<StringList>(list_of("lazy")("jumps")("The"))); + deps.add("the", 0, list_of("The")); + ensure_equals(sorted_keys(deps), make<StringList>(list_of("lazy")("jumps")("The")("the"))); + deps.add("fox", 0, list_of("The"), list_of("jumps")); + ensure_equals(sorted_keys(deps), make<StringList>(list_of("lazy")("The")("the")("fox")("jumps"))); + deps.add("the", 0, list_of("The")); // same, see if cache works + ensure_equals(sorted_keys(deps), make<StringList>(list_of("lazy")("The")("the")("fox")("jumps"))); + deps.add("jumps", 0, empty, list_of("over")); // update jumps deps + ensure_equals(sorted_keys(deps), make<StringList>(list_of("lazy")("The")("the")("fox")("jumps"))); +/*==========================================================================*| + // It drives me nuts that this test doesn't work in the test + // framework, because -- for reasons unknown -- running the test + // framework on Mac OS X 10.5 Leopard and Windows XP Pro, the catch + // clause below doesn't catch the exception. Something about the TUT + // test framework?!? The identical code works fine in a standalone + // test program. Commenting out the test for now, in hopes that our + // real builds will be able to catch Cycle exceptions... + try + { + // We've already specified fox -> jumps and jumps -> over. Try an + // impossible constraint. + deps.add("over", 0, empty, list_of("fox")); + } + catch (const StringDeps::Cycle& e) + { + std::cout << "Cycle detected: " << e.what() << '\n'; + // It's legal to add() an impossible constraint because we don't + // detect the cycle until sort(). So sort() can't know the minimum set + // of nodes to remove to make the StringDeps object valid again. + // Therefore we must break the cycle by hand. + deps.remove("over"); + } +|*==========================================================================*/ + deps.add("dog.", 0, list_of("yellow")("lazy")); + ensure_equals(instance_from_range< std::set<std::string> >(deps.get_after_range("dog.")), + make< std::set<std::string> >(list_of("lazy")("yellow"))); + ensure_equals(sorted_keys(deps), make<StringList>(list_of("lazy")("The")("the")("fox")("jumps")("dog."))); + deps.add("quick", 0, list_of("The"), list_of("fox")("brown")); + ensure_equals(sorted_keys(deps), make<StringList>(list_of("lazy")("The")("the")("quick")("fox")("jumps")("dog."))); + deps.add("over", 0, list_of("jumps"), list_of("yellow")("the")); + ensure_equals(sorted_keys(deps), make<StringList>(list_of("lazy")("The")("quick")("fox")("jumps")("over")("the")("dog."))); + deps.add("yellow", 0, list_of("the"), list_of("lazy")); + ensure_equals(sorted_keys(deps), make<StringList>(list_of("The")("quick")("fox")("jumps")("over")("the")("yellow")("lazy")("dog."))); + deps.add("brown"); + // By now the dependencies are pretty well in place. A change to THIS + // order should be viewed with suspicion. + ensure_equals(sorted_keys(deps), make<StringList>(list_of("The")("quick")("brown")("fox")("jumps")("over")("the")("yellow")("lazy")("dog."))); + + StringList keys(make<StringList>(list_of("The")("brown")("dog.")("fox")("jumps")("lazy")("over")("quick")("the")("yellow"))); + ensure_equals(instance_from_range<StringList>(deps.get_key_range()), keys); +#if (! defined(__GNUC__)) || (__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ > 3) + // This is the succinct way, works on modern compilers + ensure_equals(instance_from_range<StringList>(make_transform_range(deps.get_range(), extract_key)), keys); +#else // gcc 3.3 + StringDeps::range got_range(deps.get_range()); + StringDeps::iterator kni = got_range.begin(), knend = got_range.end(); + StringList::iterator ki = keys.begin(), kend = keys.end(); + for ( ; kni != knend && ki != kend; ++kni, ++ki) + { + ensure_equals(kni->first, *ki); + } + ensure("get_range() returns proper length", kni == knend && ki == kend); +#endif // gcc 3.3 + // blow off get_node_range() because they're all LLDependenciesEmpty instances + } + + template<> template<> + void deps_object::test<2>() + { + typedef LLDependencies<std::string, int> NameIndexDeps; + NameIndexDeps nideps; + const NameIndexDeps& const_nideps(nideps); + nideps.add("def", 2, list_of("ghi")); + nideps.add("ghi", 3); + nideps.add("abc", 1, list_of("def")); + NameIndexDeps::range range(nideps.get_range()); + ensure_equals(range.begin()->first, "abc"); + ensure_equals(range.begin()->second, 1); + range.begin()->second = 0; + range.begin()->second = 1; + NameIndexDeps::const_range const_range(const_nideps.get_range()); + NameIndexDeps::const_iterator const_iterator(const_range.begin()); + ++const_iterator; + ensure_equals(const_iterator->first, "def"); + ensure_equals(const_iterator->second, 2); + NameIndexDeps::node_range node_range(nideps.get_node_range()); + ensure_equals(instance_from_range<std::vector<int> >(node_range), make< std::vector<int> >(list_of(1)(2)(3))); + *node_range.begin() = 0; + *node_range.begin() = 1; + NameIndexDeps::const_node_range const_node_range(const_nideps.get_node_range()); + ensure_equals(instance_from_range<std::vector<int> >(const_node_range), make< std::vector<int> >(list_of(1)(2)(3))); + NameIndexDeps::const_key_range const_key_range(const_nideps.get_key_range()); + ensure_equals(instance_from_range<StringList>(const_key_range), make<StringList>(list_of("abc")("def")("ghi"))); + NameIndexDeps::sorted_range sorted(const_nideps.sort()); + NameIndexDeps::sorted_iterator sortiter(sorted.begin()); + ensure_equals(sortiter->first, "ghi"); + ensure_equals(sortiter->second, 3); + + // test all iterator-flavored versions of get_after_range() + StringList def(make<StringList>(list_of("def"))); + ensure("empty abc before list", is_empty(nideps.get_before_range(nideps.get_range().begin()))); + ensure_equals(instance_from_range<StringList>(nideps.get_after_range(nideps.get_range().begin())), + def); + ensure_equals(instance_from_range<StringList>(const_nideps.get_after_range(const_nideps.get_range().begin())), + def); + ensure_equals(instance_from_range<StringList>(nideps.get_after_range(nideps.get_node_range().begin())), + def); + ensure_equals(instance_from_range<StringList>(const_nideps.get_after_range(const_nideps.get_node_range().begin())), + def); + ensure_equals(instance_from_range<StringList>(nideps.get_after_range(nideps.get_key_range().begin())), + def); + // advance from "ghi" to "def", which must come after "ghi" + ++sortiter; + ensure_equals(instance_from_range<StringList>(const_nideps.get_after_range(sortiter)), + make<StringList>(list_of("ghi"))); + } +} // namespace tut diff --git a/indra/llcommon/tests/llerror_test.cpp b/indra/llcommon/tests/llerror_test.cpp new file mode 100644 index 0000000000..1558df231a --- /dev/null +++ b/indra/llcommon/tests/llerror_test.cpp @@ -0,0 +1,768 @@ +/** + * @file llerror_test.cpp + * @date December 2006 + * @brief error unit tests + * + * $LicenseInfo:firstyear=2006&license=viewergpl$ + * + * Copyright (c) 2006-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include <vector> + +#include "linden_common.h" + +#include "../llerror.h" + +#include "../llerrorcontrol.h" +#include "../llsd.h" + +#include "../test/lltut.h" + +namespace +{ + void test_that_error_h_includes_enough_things_to_compile_a_message() + { + llinfos << "!" << llendl; + } +} + +namespace +{ + static bool fatalWasCalled; + void fatalCall(const std::string&) { fatalWasCalled = true; } + + class TestRecorder : public LLError::Recorder + { + public: + TestRecorder() : mWantsTime(false) { } + ~TestRecorder() { LLError::removeRecorder(this); } + + void recordMessage(LLError::ELevel level, + const std::string& message) + { + mMessages.push_back(message); + } + + int countMessages() { return (int) mMessages.size(); } + void clearMessages() { mMessages.clear(); } + + void setWantsTime(bool t) { mWantsTime = t; } + bool wantsTime() { return mWantsTime; } + + std::string message(int n) + { + std::ostringstream test_name; + test_name << "testing message " << n << ", not enough messages"; + + tut::ensure(test_name.str(), n < countMessages()); + return mMessages[n]; + } + + private: + typedef std::vector<std::string> MessageVector; + MessageVector mMessages; + + bool mWantsTime; + }; +} + +namespace tut +{ + struct ErrorTestData + { + TestRecorder mRecorder; + LLError::Settings* mPriorErrorSettings; + + ErrorTestData() + { + fatalWasCalled = false; + + mPriorErrorSettings = LLError::saveAndResetSettings(); + LLError::setDefaultLevel(LLError::LEVEL_DEBUG); + LLError::setFatalFunction(fatalCall); + LLError::addRecorder(&mRecorder); + } + + ~ErrorTestData() + { + LLError::removeRecorder(&mRecorder); + LLError::restoreSettings(mPriorErrorSettings); + } + + void ensure_message_count(int expectedCount) + { + ensure_equals("message count", mRecorder.countMessages(), expectedCount); + } + + void ensure_message_contains(int n, const std::string& expectedText) + { + std::ostringstream test_name; + test_name << "testing message " << n; + + ensure_contains(test_name.str(), mRecorder.message(n), expectedText); + } + + void ensure_message_does_not_contain(int n, const std::string& expectedText) + { + std::ostringstream test_name; + test_name << "testing message " << n; + + ensure_does_not_contain(test_name.str(), mRecorder.message(n), expectedText); + } + }; + + typedef test_group<ErrorTestData> ErrorTestGroup; + typedef ErrorTestGroup::object ErrorTestObject; + + ErrorTestGroup errorTestGroup("error"); + + template<> template<> + void ErrorTestObject::test<1>() + // basic test of output + { + llinfos << "test" << llendl; + llinfos << "bob" << llendl; + + ensure_message_contains(0, "test"); + ensure_message_contains(1, "bob"); + } +} + +namespace +{ + void writeSome() + { + lldebugs << "one" << llendl; + llinfos << "two" << llendl; + llwarns << "three" << llendl; + llerrs << "four" << llendl; + // fatal messages write out and addtional "error" message + } +}; + +namespace tut +{ + template<> template<> + void ErrorTestObject::test<2>() + // messages are filtered based on default level + { + LLError::setDefaultLevel(LLError::LEVEL_DEBUG); + writeSome(); + ensure_message_contains(0, "one"); + ensure_message_contains(1, "two"); + ensure_message_contains(2, "three"); + ensure_message_contains(3, "error"); + ensure_message_contains(4, "four"); + ensure_message_count(5); + + LLError::setDefaultLevel(LLError::LEVEL_INFO); + writeSome(); + ensure_message_contains(5, "two"); + ensure_message_contains(6, "three"); + ensure_message_contains(7, "error"); + ensure_message_contains(8, "four"); + ensure_message_count(9); + + LLError::setDefaultLevel(LLError::LEVEL_WARN); + writeSome(); + ensure_message_contains(9, "three"); + ensure_message_contains(10, "error"); + ensure_message_contains(11, "four"); + ensure_message_count(12); + + LLError::setDefaultLevel(LLError::LEVEL_ERROR); + writeSome(); + ensure_message_contains(12, "error"); + ensure_message_contains(13, "four"); + ensure_message_count(14); + + LLError::setDefaultLevel(LLError::LEVEL_NONE); + writeSome(); + ensure_message_count(14); + } + + template<> template<> + void ErrorTestObject::test<3>() + // error type string in output + { + writeSome(); + ensure_message_contains(0, "DEBUG: "); + ensure_message_contains(1, "INFO: "); + ensure_message_contains(2, "WARNING: "); + ensure_message_does_not_contain(3, "ERROR"); + ensure_message_contains(4, "ERROR: "); + ensure_message_count(5); + } + + template<> template<> + void ErrorTestObject::test<4>() + // file abbreviation + { + std::string thisFile = __FILE__; + std::string abbreviateFile = LLError::abbreviateFile(thisFile); + + ensure_ends_with("file name abbreviation", + abbreviateFile, + "llcommon/tests/llerror_test.cpp" + ); + ensure_does_not_contain("file name abbreviation", + abbreviateFile, "indra"); + + std::string someFile = +#if LL_WINDOWS + "C:/amy/bob/cam.cpp" +#else + "/amy/bob/cam.cpp" +#endif + ; + std::string someAbbreviation = LLError::abbreviateFile(someFile); + + ensure_equals("non-indra file abbreviation", + someAbbreviation, someFile); + } +} + +namespace +{ + std::string locationString(int line) + { + std::ostringstream location; + location << LLError::abbreviateFile(__FILE__) + << "(" << line << ") : "; + + return location.str(); + } + + std::string writeReturningLocation() + { + llinfos << "apple" << llendl; int this_line = __LINE__; + return locationString(this_line); + } + + std::string writeReturningLocationAndFunction() + { + llinfos << "apple" << llendl; int this_line = __LINE__; + return locationString(this_line) + __FUNCTION__; + } + + std::string errorReturningLocation() + { + llerrs << "die" << llendl; int this_line = __LINE__; + return locationString(this_line); + } +} + +namespace tut +{ + template<> template<> + void ErrorTestObject::test<5>() + // file and line information in log messages + { + std::string location = writeReturningLocation(); + // expecting default to not print location information + + LLError::setPrintLocation(true); + writeReturningLocation(); + + LLError::setPrintLocation(false); + writeReturningLocation(); + + ensure_message_does_not_contain(0, location); + ensure_message_contains(1, location); + ensure_message_does_not_contain(2, location); + } +} + +/* The following helper functions and class members all log a simple message + from some particular function scope. Each function takes a bool argument + that indicates if it should log its own name or not (in the manner that + existing log messages often do.) The functions all return their C++ + name so that test can be substantial mechanized. + */ + +std::string logFromGlobal(bool id) +{ + llinfos << (id ? "logFromGlobal: " : "") << "hi" << llendl; + return "logFromGlobal"; +} + +static std::string logFromStatic(bool id) +{ + llinfos << (id ? "logFromStatic: " : "") << "hi" << llendl; + return "logFromStatic"; +} + +namespace +{ + std::string logFromAnon(bool id) + { + llinfos << (id ? "logFromAnon: " : "") << "hi" << llendl; + return "logFromAnon"; + } +} + +namespace Foo { + std::string logFromNamespace(bool id) + { + llinfos << (id ? "Foo::logFromNamespace: " : "") << "hi" << llendl; + //return "Foo::logFromNamespace"; + // there is no standard way to get the namespace name, hence + // we won't be testing for it + return "logFromNamespace"; + } +} + +namespace +{ + class ClassWithNoLogType { + public: + std::string logFromMember(bool id) + { + llinfos << (id ? "ClassWithNoLogType::logFromMember: " : "") << "hi" << llendl; + return "ClassWithNoLogType::logFromMember"; + } + static std::string logFromStatic(bool id) + { + llinfos << (id ? "ClassWithNoLogType::logFromStatic: " : "") << "hi" << llendl; + return "ClassWithNoLogType::logFromStatic"; + } + }; + + class ClassWithLogType { + LOG_CLASS(ClassWithLogType); + public: + std::string logFromMember(bool id) + { + llinfos << (id ? "ClassWithLogType::logFromMember: " : "") << "hi" << llendl; + return "ClassWithLogType::logFromMember"; + } + static std::string logFromStatic(bool id) + { + llinfos << (id ? "ClassWithLogType::logFromStatic: " : "") << "hi" << llendl; + return "ClassWithLogType::logFromStatic"; + } + }; + + std::string logFromNamespace(bool id) { return Foo::logFromNamespace(id); } + std::string logFromClassWithNoLogTypeMember(bool id) { ClassWithNoLogType c; return c.logFromMember(id); } + std::string logFromClassWithNoLogTypeStatic(bool id) { return ClassWithNoLogType::logFromStatic(id); } + std::string logFromClassWithLogTypeMember(bool id) { ClassWithLogType c; return c.logFromMember(id); } + std::string logFromClassWithLogTypeStatic(bool id) { return ClassWithLogType::logFromStatic(id); } + + void ensure_has(const std::string& message, + const std::string& actual, const std::string& expected) + { + std::string::size_type n1 = actual.find(expected); + if (n1 == std::string::npos) + { + std::stringstream ss; + ss << message << ": " << "expected to find a copy of " << expected + << " in actual " << actual; + throw tut::failure(ss.str().c_str()); + } + } + + typedef std::string (*LogFromFunction)(bool); + void testLogName(TestRecorder& recorder, LogFromFunction f, + const std::string& class_name = "") + { + recorder.clearMessages(); + std::string name = f(false); + f(true); + + std::string messageWithoutName = recorder.message(0); + std::string messageWithName = recorder.message(1); + + ensure_has(name + " logged without name", + messageWithoutName, name); + ensure_has(name + " logged with name", + messageWithName, name); + + if (!class_name.empty()) + { + ensure_has(name + "logged without name", + messageWithoutName, class_name); + ensure_has(name + "logged with name", + messageWithName, class_name); + } + } +} + +namespace tut +{ + template<> template<> + // class/function information in output + void ErrorTestObject::test<6>() + { + testLogName(mRecorder, logFromGlobal); + testLogName(mRecorder, logFromStatic); + testLogName(mRecorder, logFromAnon); + testLogName(mRecorder, logFromNamespace); + //testLogName(mRecorder, logFromClassWithNoLogTypeMember, "ClassWithNoLogType"); + //testLogName(mRecorder, logFromClassWithNoLogTypeStatic, "ClassWithNoLogType"); + // XXX: figure out what the exepcted response is for these + testLogName(mRecorder, logFromClassWithLogTypeMember, "ClassWithLogType"); + testLogName(mRecorder, logFromClassWithLogTypeStatic, "ClassWithLogType"); + } +} + +namespace +{ + std::string innerLogger() + { + llinfos << "inside" << llendl; + return "moo"; + } + + std::string outerLogger() + { + llinfos << "outside(" << innerLogger() << ")" << llendl; + return "bar"; + } + + void uberLogger() + { + llinfos << "uber(" << outerLogger() << "," << innerLogger() << ")" << llendl; + } + + class LogWhileLogging + { + public: + void print(std::ostream& out) const + { + llinfos << "logging" << llendl; + out << "baz"; + } + }; + + std::ostream& operator<<(std::ostream& out, const LogWhileLogging& l) + { l.print(out); return out; } + + void metaLogger() + { + LogWhileLogging l; + llinfos << "meta(" << l << ")" << llendl; + } + +} + +namespace tut +{ + template<> template<> + // handle nested logging + void ErrorTestObject::test<7>() + { + outerLogger(); + ensure_message_contains(0, "inside"); + ensure_message_contains(1, "outside(moo)"); + ensure_message_count(2); + + uberLogger(); + ensure_message_contains(2, "inside"); + ensure_message_contains(3, "inside"); + ensure_message_contains(4, "outside(moo)"); + ensure_message_contains(5, "uber(bar,moo)"); + ensure_message_count(6); + + metaLogger(); + ensure_message_contains(6, "logging"); + ensure_message_contains(7, "meta(baz)"); + ensure_message_count(8); + } + + template<> template<> + // special handling of llerrs calls + void ErrorTestObject::test<8>() + { + LLError::setPrintLocation(false); + std::string location = errorReturningLocation(); + + ensure_message_contains(0, location + "error"); + ensure_message_contains(1, "die"); + ensure_message_count(2); + + ensure("fatal callback called", fatalWasCalled); + } +} + +namespace +{ + std::string roswell() + { + return "1947-07-08T03:04:05Z"; + } + + void ufoSighting() + { + llinfos << "ufo" << llendl; + } +} + +namespace tut +{ + template<> template<> + // time in output (for recorders that need it) + void ErrorTestObject::test<9>() + { + LLError::setTimeFunction(roswell); + + mRecorder.setWantsTime(false); + ufoSighting(); + ensure_message_contains(0, "ufo"); + ensure_message_does_not_contain(0, roswell()); + + mRecorder.setWantsTime(true); + ufoSighting(); + ensure_message_contains(1, "ufo"); + ensure_message_contains(1, roswell()); + } + + template<> template<> + // output order + void ErrorTestObject::test<10>() + { + LLError::setPrintLocation(true); + LLError::setTimeFunction(roswell); + mRecorder.setWantsTime(true); + std::string locationAndFunction = writeReturningLocationAndFunction(); + + ensure_equals("order is time type location function message", + mRecorder.message(0), + roswell() + " INFO: " + locationAndFunction + ": apple"); + } + + template<> template<> + // multiple recorders + void ErrorTestObject::test<11>() + { + TestRecorder altRecorder; + LLError::addRecorder(&altRecorder); + + llinfos << "boo" << llendl; + + ensure_message_contains(0, "boo"); + ensure_equals("alt recorder count", altRecorder.countMessages(), 1); + ensure_contains("alt recorder message 0", altRecorder.message(0), "boo"); + + LLError::setTimeFunction(roswell); + + TestRecorder anotherRecorder; + anotherRecorder.setWantsTime(true); + LLError::addRecorder(&anotherRecorder); + + llinfos << "baz" << llendl; + + std::string when = roswell(); + + ensure_message_does_not_contain(1, when); + ensure_equals("alt recorder count", altRecorder.countMessages(), 2); + ensure_does_not_contain("alt recorder message 1", altRecorder.message(1), when); + ensure_equals("another recorder count", anotherRecorder.countMessages(), 1); + ensure_contains("another recorder message 0", anotherRecorder.message(0), when); + } +} + +class TestAlpha +{ + LOG_CLASS(TestAlpha); +public: + static void doDebug() { lldebugs << "add dice" << llendl; } + static void doInfo() { llinfos << "any idea" << llendl; } + static void doWarn() { llwarns << "aim west" << llendl; } + static void doError() { llerrs << "ate eels" << llendl; } + static void doAll() { doDebug(); doInfo(); doWarn(); doError(); } +}; + +class TestBeta +{ + LOG_CLASS(TestBeta); +public: + static void doDebug() { lldebugs << "bed down" << llendl; } + static void doInfo() { llinfos << "buy iron" << llendl; } + static void doWarn() { llwarns << "bad word" << llendl; } + static void doError() { llerrs << "big easy" << llendl; } + static void doAll() { doDebug(); doInfo(); doWarn(); doError(); } +}; + +namespace tut +{ + template<> template<> + // filtering by class + void ErrorTestObject::test<12>() + { + LLError::setDefaultLevel(LLError::LEVEL_WARN); + LLError::setClassLevel("TestBeta", LLError::LEVEL_INFO); + + TestAlpha::doAll(); + TestBeta::doAll(); + + ensure_message_contains(0, "aim west"); + ensure_message_contains(1, "error"); + ensure_message_contains(2, "ate eels"); + ensure_message_contains(3, "buy iron"); + ensure_message_contains(4, "bad word"); + ensure_message_contains(5, "error"); + ensure_message_contains(6, "big easy"); + ensure_message_count(7); + } + + template<> template<> + // filtering by function, and that it will override class filtering + void ErrorTestObject::test<13>() + { + LLError::setDefaultLevel(LLError::LEVEL_DEBUG); + LLError::setClassLevel("TestBeta", LLError::LEVEL_WARN); + LLError::setFunctionLevel("TestBeta::doInfo", LLError::LEVEL_DEBUG); + LLError::setFunctionLevel("TestBeta::doError", LLError::LEVEL_NONE); + + TestBeta::doAll(); + ensure_message_contains(0, "buy iron"); + ensure_message_contains(1, "bad word"); + ensure_message_count(2); + } + + template<> template<> + // filtering by file + // and that it is overridden by both class and function filtering + void ErrorTestObject::test<14>() + { + LLError::setDefaultLevel(LLError::LEVEL_DEBUG); + LLError::setFileLevel(LLError::abbreviateFile(__FILE__), + LLError::LEVEL_WARN); + LLError::setClassLevel("TestAlpha", LLError::LEVEL_INFO); + LLError::setFunctionLevel("TestAlpha::doError", + LLError::LEVEL_NONE); + LLError::setFunctionLevel("TestBeta::doError", + LLError::LEVEL_NONE); + + TestAlpha::doAll(); + TestBeta::doAll(); + ensure_message_contains(0, "any idea"); + ensure_message_contains(1, "aim west"); + ensure_message_contains(2, "bad word"); + ensure_message_count(3); + } + + template<> template<> + // proper cached, efficient lookup of filtering + void ErrorTestObject::test<15>() + { + LLError::setDefaultLevel(LLError::LEVEL_NONE); + + TestAlpha::doInfo(); + ensure_message_count(0); + ensure_equals("first check", LLError::shouldLogCallCount(), 1); + TestAlpha::doInfo(); + ensure_message_count(0); + ensure_equals("second check", LLError::shouldLogCallCount(), 1); + + LLError::setClassLevel("TestAlpha", LLError::LEVEL_DEBUG); + TestAlpha::doInfo(); + ensure_message_count(1); + ensure_equals("third check", LLError::shouldLogCallCount(), 2); + TestAlpha::doInfo(); + ensure_message_count(2); + ensure_equals("fourth check", LLError::shouldLogCallCount(), 2); + + LLError::setClassLevel("TestAlpha", LLError::LEVEL_WARN); + TestAlpha::doInfo(); + ensure_message_count(2); + ensure_equals("fifth check", LLError::shouldLogCallCount(), 3); + TestAlpha::doInfo(); + ensure_message_count(2); + ensure_equals("sixth check", LLError::shouldLogCallCount(), 3); + } + + template<> template<> + // configuration from LLSD + void ErrorTestObject::test<16>() + { + std::string this_file = LLError::abbreviateFile(__FILE__); + LLSD config; + config["print-location"] = true; + config["default-level"] = "DEBUG"; + + LLSD set1; + set1["level"] = "WARN"; + set1["files"][0] = this_file; + + LLSD set2; + set2["level"] = "INFO"; + set2["classes"][0] = "TestAlpha"; + + LLSD set3; + set3["level"] = "NONE"; + set3["functions"][0] = "TestAlpha::doError"; + set3["functions"][1] = "TestBeta::doError"; + + config["settings"][0] = set1; + config["settings"][1] = set2; + config["settings"][2] = set3; + + LLError::configure(config); + + TestAlpha::doAll(); + TestBeta::doAll(); + ensure_message_contains(0, "any idea"); + ensure_message_contains(0, this_file); + ensure_message_contains(1, "aim west"); + ensure_message_contains(2, "bad word"); + ensure_message_count(3); + + // make sure reconfiguring works + LLSD config2; + config2["default-level"] = "WARN"; + + LLError::configure(config2); + + TestAlpha::doAll(); + TestBeta::doAll(); + ensure_message_contains(3, "aim west"); + ensure_message_does_not_contain(3, this_file); + ensure_message_contains(4, "error"); + ensure_message_contains(5, "ate eels"); + ensure_message_contains(6, "bad word"); + ensure_message_contains(7, "error"); + ensure_message_contains(8, "big easy"); + ensure_message_count(9); + } +} + +/* Tests left: + handling of classes without LOG_CLASS + + live update of filtering from file + + syslog recorder + file recorder + cerr/stderr recorder + fixed buffer recorder + windows recorder + + mutex use when logging (?) + strange careful about to crash handling (?) +*/ diff --git a/indra/llcommon/tests/lleventcoro_test.cpp b/indra/llcommon/tests/lleventcoro_test.cpp new file mode 100644 index 0000000000..3a2cda7735 --- /dev/null +++ b/indra/llcommon/tests/lleventcoro_test.cpp @@ -0,0 +1,782 @@ +/** + * @file coroutine_test.cpp + * @author Nat Goodspeed + * @date 2009-04-22 + * @brief Test for coroutine. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +/*****************************************************************************/ +// test<1>() is cloned from a Boost.Coroutine example program whose copyright +// info is reproduced here: +/*---------------------------------------------------------------------------*/ +// Copyright (c) 2006, Giovanni P. Deretta +// +// This code may be used under either of the following two licences: +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. OF SUCH DAMAGE. +// +// Or: +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +/*****************************************************************************/ + +// On some platforms, Boost.Coroutine must #define magic symbols before +// #including platform-API headers. Naturally, that's ineffective unless the +// Boost.Coroutine #include is the *first* #include of the platform header. +// That means that client code must generally #include Boost.Coroutine headers +// before anything else. +#include <boost/coroutine/coroutine.hpp> +// Normally, lleventcoro.h obviates future.hpp. We only include this because +// we implement a "by hand" test of future functionality. +#include <boost/coroutine/future.hpp> +#include <boost/bind.hpp> +#include <boost/range.hpp> + +#include "linden_common.h" + +#include <iostream> +#include <string> + +#include "../test/lltut.h" +#include "llsd.h" +#include "llevents.h" +#include "tests/wrapllerrs.h" +#include "stringize.h" +#include "lleventcoro.h" +#include "../test/debug.h" + +/***************************************************************************** +* from the banana.cpp example program borrowed for test<1>() +*****************************************************************************/ +namespace coroutines = boost::coroutines; +using coroutines::coroutine; + +template<typename Iter> +bool match(Iter first, Iter last, std::string match) { + std::string::iterator i = match.begin(); + i != match.end(); + for(; (first != last) && (i != match.end()); ++i) { + if (*first != *i) + return false; + ++first; + } + return i == match.end(); +} + +template<typename BidirectionalIterator> +BidirectionalIterator +match_substring(BidirectionalIterator begin, + BidirectionalIterator end, + std::string xmatch, + BOOST_DEDUCED_TYPENAME coroutine<BidirectionalIterator(void)>::self& self) { + BidirectionalIterator begin_ = begin; + for(; begin != end; ++begin) + if(match(begin, end, xmatch)) { + self.yield(begin); + } + return end; +} + +typedef coroutine<std::string::iterator(void)> match_coroutine_type; + +/***************************************************************************** +* Test helpers +*****************************************************************************/ +// I suspect this will be typical of coroutines used in Linden software +typedef boost::coroutines::coroutine<void()> coroutine_type; + +/// Simulate an event API whose response is immediate: sent on receipt of the +/// initial request, rather than after some delay. This is the case that +/// distinguishes postAndWait() from calling post(), then calling +/// waitForEventOn(). +class ImmediateAPI +{ +public: + ImmediateAPI(): + mPump("immediate", true) + { + mPump.listen("API", boost::bind(&ImmediateAPI::operator(), this, _1)); + } + + LLEventPump& getPump() { return mPump; } + + // Invoke this with an LLSD map containing: + // ["value"]: Integer value. We will reply with ["value"] + 1. + // ["reply"]: Name of LLEventPump on which to send success response. + // ["error"]: Name of LLEventPump on which to send error response. + // ["fail"]: Presence of this key selects ["error"], else ["success"] as + // the name of the pump on which to send the response. + bool operator()(const LLSD& event) const + { + LLSD::Integer value(event["value"]); + LLSD::String replyPumpName(event.has("fail")? "error" : "reply"); + LLEventPumps::instance().obtain(event[replyPumpName]).post(value + 1); + return false; + } + +private: + LLEventStream mPump; +}; + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct coroutine_data + { + // Define coroutine bodies as methods here so they can use ensure*() + + void explicit_wait(coroutine_type::self& self) + { + BEGIN + { + // ... do whatever preliminary stuff must happen ... + + // declare the future + boost::coroutines::future<LLSD> future(self); + // tell the future what to wait for + LLTempBoundListener connection( + LLEventPumps::instance().obtain("source").listen("coro", voidlistener(boost::coroutines::make_callback(future)))); + ensure("Not yet", ! future); + // attempting to dereference ("resolve") the future causes the calling + // coroutine to wait for it + debug("about to wait"); + result = *future; + ensure("Got it", future); + } + END + } + + void waitForEventOn1(coroutine_type::self& self) + { + BEGIN + { + result = waitForEventOn(self, "source"); + } + END + } + + void waitForEventOn2(coroutine_type::self& self) + { + BEGIN + { + LLEventWithID pair = waitForEventOn(self, "reply", "error"); + result = pair.first; + which = pair.second; + debug(STRINGIZE("result = " << result << ", which = " << which)); + } + END + } + + void postAndWait1(coroutine_type::self& self) + { + BEGIN + { + result = postAndWait(self, + LLSD().insert("value", 17), // request event + immediateAPI.getPump(), // requestPump + "reply1", // replyPump + "reply"); // request["reply"] = name + } + END + } + + void postAndWait2(coroutine_type::self& self) + { + BEGIN + { + LLEventWithID pair = ::postAndWait2(self, + LLSD().insert("value", 18), + immediateAPI.getPump(), + "reply2", + "error2", + "reply", + "error"); + result = pair.first; + which = pair.second; + debug(STRINGIZE("result = " << result << ", which = " << which)); + } + END + } + + void postAndWait2_1(coroutine_type::self& self) + { + BEGIN + { + LLEventWithID pair = ::postAndWait2(self, + LLSD().insert("value", 18).insert("fail", LLSD()), + immediateAPI.getPump(), + "reply2", + "error2", + "reply", + "error"); + result = pair.first; + which = pair.second; + debug(STRINGIZE("result = " << result << ", which = " << which)); + } + END + } + + void coroPump(coroutine_type::self& self) + { + BEGIN + { + LLCoroEventPump waiter; + replyName = waiter.getName(); + result = waiter.wait(self); + } + END + } + + void coroPumpPost(coroutine_type::self& self) + { + BEGIN + { + LLCoroEventPump waiter; + result = waiter.postAndWait(self, LLSD().insert("value", 17), + immediateAPI.getPump(), "reply"); + } + END + } + + void coroPumps(coroutine_type::self& self) + { + BEGIN + { + LLCoroEventPumps waiter; + replyName = waiter.getName0(); + errorName = waiter.getName1(); + LLEventWithID pair(waiter.wait(self)); + result = pair.first; + which = pair.second; + } + END + } + + void coroPumpsNoEx(coroutine_type::self& self) + { + BEGIN + { + LLCoroEventPumps waiter; + replyName = waiter.getName0(); + errorName = waiter.getName1(); + result = waiter.waitWithException(self); + } + END + } + + void coroPumpsEx(coroutine_type::self& self) + { + BEGIN + { + LLCoroEventPumps waiter; + replyName = waiter.getName0(); + errorName = waiter.getName1(); + try + { + result = waiter.waitWithException(self); + debug("no exception"); + } + catch (const LLErrorEvent& e) + { + debug(STRINGIZE("exception " << e.what())); + errordata = e.getData(); + } + } + END + } + + void coroPumpsNoLog(coroutine_type::self& self) + { + BEGIN + { + LLCoroEventPumps waiter; + replyName = waiter.getName0(); + errorName = waiter.getName1(); + result = waiter.waitWithLog(self); + } + END + } + + void coroPumpsLog(coroutine_type::self& self) + { + BEGIN + { + LLCoroEventPumps waiter; + replyName = waiter.getName0(); + errorName = waiter.getName1(); + WrapLL_ERRS capture; + try + { + result = waiter.waitWithLog(self); + debug("no exception"); + } + catch (const WrapLL_ERRS::FatalException& e) + { + debug(STRINGIZE("exception " << e.what())); + threw = e.what(); + } + } + END + } + + void coroPumpsPost(coroutine_type::self& self) + { + BEGIN + { + LLCoroEventPumps waiter; + LLEventWithID pair(waiter.postAndWait(self, LLSD().insert("value", 23), + immediateAPI.getPump(), "reply", "error")); + result = pair.first; + which = pair.second; + } + END + } + + void coroPumpsPost_1(coroutine_type::self& self) + { + BEGIN + { + LLCoroEventPumps waiter; + LLEventWithID pair( + waiter.postAndWait(self, LLSD().insert("value", 23).insert("fail", LLSD()), + immediateAPI.getPump(), "reply", "error")); + result = pair.first; + which = pair.second; + } + END + } + + void coroPumpsPostNoEx(coroutine_type::self& self) + { + BEGIN + { + LLCoroEventPumps waiter; + result = waiter.postAndWaitWithException(self, LLSD().insert("value", 8), + immediateAPI.getPump(), "reply", "error"); + } + END + } + + void coroPumpsPostEx(coroutine_type::self& self) + { + BEGIN + { + LLCoroEventPumps waiter; + try + { + result = waiter.postAndWaitWithException(self, + LLSD().insert("value", 9).insert("fail", LLSD()), + immediateAPI.getPump(), "reply", "error"); + debug("no exception"); + } + catch (const LLErrorEvent& e) + { + debug(STRINGIZE("exception " << e.what())); + errordata = e.getData(); + } + } + END + } + + void coroPumpsPostNoLog(coroutine_type::self& self) + { + BEGIN + { + LLCoroEventPumps waiter; + result = waiter.postAndWaitWithLog(self, LLSD().insert("value", 30), + immediateAPI.getPump(), "reply", "error"); + } + END + } + + void coroPumpsPostLog(coroutine_type::self& self) + { + BEGIN + { + LLCoroEventPumps waiter; + WrapLL_ERRS capture; + try + { + result = waiter.postAndWaitWithLog(self, + LLSD().insert("value", 31).insert("fail", LLSD()), + immediateAPI.getPump(), "reply", "error"); + debug("no exception"); + } + catch (const WrapLL_ERRS::FatalException& e) + { + debug(STRINGIZE("exception " << e.what())); + threw = e.what(); + } + } + END + } + + void ensure_done(coroutine_type& coro) + { + ensure("coroutine complete", ! coro); + } + + ImmediateAPI immediateAPI; + std::string replyName, errorName, threw; + LLSD result, errordata; + int which; + }; + typedef test_group<coroutine_data> coroutine_group; + typedef coroutine_group::object object; + coroutine_group coroutinegrp("coroutine"); + + template<> template<> + void object::test<1>() + { + set_test_name("From banana.cpp example program in Boost.Coroutine distro"); + std::string buffer = "banananana"; + std::string match = "nana"; + std::string::iterator begin = buffer.begin(); + std::string::iterator end = buffer.end(); + +#if defined(BOOST_CORO_POSIX_IMPL) +// std::cout << "Using Boost.Coroutine " << BOOST_CORO_POSIX_IMPL << '\n'; +#else +// std::cout << "Using non-Posix Boost.Coroutine implementation" << std::endl; +#endif + + typedef std::string::iterator signature(std::string::iterator, + std::string::iterator, + std::string, + match_coroutine_type::self&); + + coroutine<std::string::iterator(void)> matcher + (boost::bind(static_cast<signature*>(match_substring), + begin, + end, + match, + _1)); + + std::string::iterator i = matcher(); +/*==========================================================================*| + while(matcher && i != buffer.end()) { + std::cout <<"Match at: "<< std::distance(buffer.begin(), i)<<'\n'; + i = matcher(); + } +|*==========================================================================*/ + size_t matches[] = { 2, 4, 6 }; + for (size_t *mi(boost::begin(matches)), *mend(boost::end(matches)); + mi != mend; ++mi, i = matcher()) + { + ensure("more", matcher); + ensure("found", i != buffer.end()); + ensure_equals("value", std::distance(buffer.begin(), i), *mi); + } + ensure("done", ! matcher); + } + + template<> template<> + void object::test<2>() + { + set_test_name("explicit_wait"); + DEBUG; + + // Construct the coroutine instance that will run explicit_wait. + // Pass the ctor a callable that accepts the coroutine_type::self + // param passed by the library. + coroutine_type coro(boost::bind(&coroutine_data::explicit_wait, this, _1)); + // Start the coroutine + coro(std::nothrow); + // When the coroutine waits for the event pump, it returns here. + debug("about to send"); + // Satisfy the wait. + LLEventPumps::instance().obtain("source").post("received"); + // Now wait for the coroutine to complete. + ensure_done(coro); + // ensure the coroutine ran and woke up again with the intended result + ensure_equals(result.asString(), "received"); + } + + template<> template<> + void object::test<3>() + { + set_test_name("waitForEventOn1"); + DEBUG; + coroutine_type coro(boost::bind(&coroutine_data::waitForEventOn1, this, _1)); + coro(std::nothrow); + debug("about to send"); + LLEventPumps::instance().obtain("source").post("received"); + debug("back from send"); + ensure_done(coro); + ensure_equals(result.asString(), "received"); + } + + template<> template<> + void object::test<4>() + { + set_test_name("waitForEventOn2 reply"); + { + DEBUG; + coroutine_type coro(boost::bind(&coroutine_data::waitForEventOn2, this, _1)); + coro(std::nothrow); + debug("about to send"); + LLEventPumps::instance().obtain("reply").post("received"); + debug("back from send"); + ensure_done(coro); + } + ensure_equals(result.asString(), "received"); + ensure_equals("which pump", which, 0); + } + + template<> template<> + void object::test<5>() + { + set_test_name("waitForEventOn2 error"); + DEBUG; + coroutine_type coro(boost::bind(&coroutine_data::waitForEventOn2, this, _1)); + coro(std::nothrow); + debug("about to send"); + LLEventPumps::instance().obtain("error").post("badness"); + debug("back from send"); + ensure_done(coro); + ensure_equals(result.asString(), "badness"); + ensure_equals("which pump", which, 1); + } + + template<> template<> + void object::test<6>() + { + set_test_name("coroPump"); + DEBUG; + coroutine_type coro(boost::bind(&coroutine_data::coroPump, this, _1)); + coro(std::nothrow); + debug("about to send"); + LLEventPumps::instance().obtain(replyName).post("received"); + debug("back from send"); + ensure_done(coro); + ensure_equals(result.asString(), "received"); + } + + template<> template<> + void object::test<7>() + { + set_test_name("coroPumps reply"); + DEBUG; + coroutine_type coro(boost::bind(&coroutine_data::coroPumps, this, _1)); + coro(std::nothrow); + debug("about to send"); + LLEventPumps::instance().obtain(replyName).post("received"); + debug("back from send"); + ensure_done(coro); + ensure_equals(result.asString(), "received"); + ensure_equals("which pump", which, 0); + } + + template<> template<> + void object::test<8>() + { + set_test_name("coroPumps error"); + DEBUG; + coroutine_type coro(boost::bind(&coroutine_data::coroPumps, this, _1)); + coro(std::nothrow); + debug("about to send"); + LLEventPumps::instance().obtain(errorName).post("badness"); + debug("back from send"); + ensure_done(coro); + ensure_equals(result.asString(), "badness"); + ensure_equals("which pump", which, 1); + } + + template<> template<> + void object::test<9>() + { + set_test_name("coroPumpsNoEx"); + DEBUG; + coroutine_type coro(boost::bind(&coroutine_data::coroPumpsNoEx, this, _1)); + coro(std::nothrow); + debug("about to send"); + LLEventPumps::instance().obtain(replyName).post("received"); + debug("back from send"); + ensure_done(coro); + ensure_equals(result.asString(), "received"); + } + + template<> template<> + void object::test<10>() + { + set_test_name("coroPumpsEx"); + DEBUG; + coroutine_type coro(boost::bind(&coroutine_data::coroPumpsEx, this, _1)); + coro(std::nothrow); + debug("about to send"); + LLEventPumps::instance().obtain(errorName).post("badness"); + debug("back from send"); + ensure_done(coro); + ensure("no result", result.isUndefined()); + ensure_equals("got error", errordata.asString(), "badness"); + } + + template<> template<> + void object::test<11>() + { + set_test_name("coroPumpsNoLog"); + DEBUG; + coroutine_type coro(boost::bind(&coroutine_data::coroPumpsNoLog, this, _1)); + coro(std::nothrow); + debug("about to send"); + LLEventPumps::instance().obtain(replyName).post("received"); + debug("back from send"); + ensure_done(coro); + ensure_equals(result.asString(), "received"); + } + + template<> template<> + void object::test<12>() + { + set_test_name("coroPumpsLog"); + DEBUG; + coroutine_type coro(boost::bind(&coroutine_data::coroPumpsLog, this, _1)); + coro(std::nothrow); + debug("about to send"); + LLEventPumps::instance().obtain(errorName).post("badness"); + debug("back from send"); + ensure_done(coro); + ensure("no result", result.isUndefined()); + ensure_contains("got error", threw, "badness"); + } + + template<> template<> + void object::test<13>() + { + set_test_name("postAndWait1"); + DEBUG; + coroutine_type coro(boost::bind(&coroutine_data::postAndWait1, this, _1)); + coro(std::nothrow); + ensure_done(coro); + ensure_equals(result.asInteger(), 18); + } + + template<> template<> + void object::test<14>() + { + set_test_name("postAndWait2"); + DEBUG; + coroutine_type coro(boost::bind(&coroutine_data::postAndWait2, this, _1)); + coro(std::nothrow); + ensure_done(coro); + ensure_equals(result.asInteger(), 19); + ensure_equals(which, 0); + } + + template<> template<> + void object::test<15>() + { + set_test_name("postAndWait2_1"); + DEBUG; + coroutine_type coro(boost::bind(&coroutine_data::postAndWait2_1, this, _1)); + coro(std::nothrow); + ensure_done(coro); + ensure_equals(result.asInteger(), 19); + ensure_equals(which, 1); + } + + template<> template<> + void object::test<16>() + { + set_test_name("coroPumpPost"); + DEBUG; + coroutine_type coro(boost::bind(&coroutine_data::coroPumpPost, this, _1)); + coro(std::nothrow); + ensure_done(coro); + ensure_equals(result.asInteger(), 18); + } + + template<> template<> + void object::test<17>() + { + set_test_name("coroPumpsPost reply"); + DEBUG; + coroutine_type coro(boost::bind(&coroutine_data::coroPumpsPost, this, _1)); + coro(std::nothrow); + ensure_done(coro); + ensure_equals(result.asInteger(), 24); + ensure_equals("which pump", which, 0); + } + + template<> template<> + void object::test<18>() + { + set_test_name("coroPumpsPost error"); + DEBUG; + coroutine_type coro(boost::bind(&coroutine_data::coroPumpsPost_1, this, _1)); + coro(std::nothrow); + ensure_done(coro); + ensure_equals(result.asInteger(), 24); + ensure_equals("which pump", which, 1); + } + + template<> template<> + void object::test<19>() + { + set_test_name("coroPumpsPostNoEx"); + DEBUG; + coroutine_type coro(boost::bind(&coroutine_data::coroPumpsPostNoEx, this, _1)); + coro(std::nothrow); + ensure_done(coro); + ensure_equals(result.asInteger(), 9); + } + + template<> template<> + void object::test<20>() + { + set_test_name("coroPumpsPostEx"); + DEBUG; + coroutine_type coro(boost::bind(&coroutine_data::coroPumpsPostEx, this, _1)); + coro(std::nothrow); + ensure_done(coro); + ensure("no result", result.isUndefined()); + ensure_equals("got error", errordata.asInteger(), 10); + } + + template<> template<> + void object::test<21>() + { + set_test_name("coroPumpsPostNoLog"); + DEBUG; + coroutine_type coro(boost::bind(&coroutine_data::coroPumpsPostNoLog, this, _1)); + coro(std::nothrow); + ensure_done(coro); + ensure_equals(result.asInteger(), 31); + } + + template<> template<> + void object::test<22>() + { + set_test_name("coroPumpsPostLog"); + DEBUG; + coroutine_type coro(boost::bind(&coroutine_data::coroPumpsPostLog, this, _1)); + coro(std::nothrow); + ensure_done(coro); + ensure("no result", result.isUndefined()); + ensure_contains("got error", threw, "32"); + } +} // namespace tut diff --git a/indra/llcommon/tests/lleventfilter_test.cpp b/indra/llcommon/tests/lleventfilter_test.cpp new file mode 100644 index 0000000000..28b909298e --- /dev/null +++ b/indra/llcommon/tests/lleventfilter_test.cpp @@ -0,0 +1,276 @@ +/** + * @file lleventfilter_test.cpp + * @author Nat Goodspeed + * @date 2009-03-06 + * @brief Test for lleventfilter. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lleventfilter.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "../test/lltut.h" +#include "stringize.h" +#include "listener.h" +#include "tests/wrapllerrs.h" + +/***************************************************************************** +* Test classes +*****************************************************************************/ +// Strictly speaking, we're testing LLEventTimeoutBase rather than the +// production LLEventTimeout (using LLTimer) because we don't want every test +// run to pause for some number of seconds until we reach a real timeout. But +// as we've carefully put all functionality except actual LLTimer calls into +// LLEventTimeoutBase, that should suffice. We're not not not trying to test +// LLTimer here. +class TestEventTimeout: public LLEventTimeoutBase +{ +public: + TestEventTimeout(): + mElapsed(true) + {} + TestEventTimeout(LLEventPump& source): + LLEventTimeoutBase(source), + mElapsed(true) + {} + + // test hook + void forceTimeout(bool timeout=true) { mElapsed = timeout; } + +protected: + virtual void setCountdown(F32 seconds) { mElapsed = false; } + virtual bool countdownElapsed() const { return mElapsed; } + +private: + bool mElapsed; +}; + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct filter_data + { + // The resemblance between this test data and that in llevents_tut.cpp + // is not coincidental. + filter_data(): + pumps(LLEventPumps::instance()), + mainloop(pumps.obtain("mainloop")), + listener0("first"), + listener1("second") + {} + LLEventPumps& pumps; + LLEventPump& mainloop; + Listener listener0; + Listener listener1; + + void check_listener(const std::string& desc, const Listener& listener, const LLSD& got) + { + ensure_equals(STRINGIZE(listener << ' ' << desc), + listener.getLastEvent(), got); + } + }; + typedef test_group<filter_data> filter_group; + typedef filter_group::object filter_object; + filter_group filtergrp("lleventfilter"); + + template<> template<> + void filter_object::test<1>() + { + set_test_name("LLEventMatching"); + LLEventPump& driver(pumps.obtain("driver")); + listener0.reset(0); + // Listener isn't derived from LLEventTrackable specifically to test + // various connection-management mechanisms. But that means we have a + // couple of transient Listener objects, one of which is listening to + // a persistent LLEventPump. Capture those connections in local + // LLTempBoundListener instances so they'll disconnect + // on destruction. + LLTempBoundListener temp1( + listener0.listenTo(driver)); + // Construct a pattern LLSD: desired Event must have a key "foo" + // containing string "bar" + LLEventMatching filter(driver, LLSD().insert("foo", "bar")); + listener1.reset(0); + LLTempBoundListener temp2( + listener1.listenTo(filter)); + driver.post(1); + check_listener("direct", listener0, LLSD(1)); + check_listener("filtered", listener1, LLSD(0)); + // Okay, construct an LLSD map matching the pattern + LLSD data; + data["foo"] = "bar"; + data["random"] = 17; + driver.post(data); + check_listener("direct", listener0, data); + check_listener("filtered", listener1, data); + } + + template<> template<> + void filter_object::test<2>() + { + set_test_name("LLEventTimeout::actionAfter()"); + LLEventPump& driver(pumps.obtain("driver")); + TestEventTimeout filter(driver); + listener0.reset(0); + LLTempBoundListener temp1( + listener0.listenTo(filter)); + // Use listener1.call() as the Action for actionAfter(), since it + // already provides a way to sense the call + listener1.reset(0); + // driver --> filter --> listener0 + filter.actionAfter(20, + boost::bind(&Listener::call, boost::ref(listener1), LLSD("timeout"))); + // Okay, (fake) timer is ticking. 'filter' can only sense the timer + // when we pump mainloop. Do that right now to take the logic path + // before either the anticipated event arrives or the timer expires. + mainloop.post(17); + check_listener("no timeout 1", listener1, LLSD(0)); + // Expected event arrives... + driver.post(1); + check_listener("event passed thru", listener0, LLSD(1)); + // Should have canceled the timer. Verify that by asserting that the + // time has expired, then pumping mainloop again. + filter.forceTimeout(); + mainloop.post(17); + check_listener("no timeout 2", listener1, LLSD(0)); + // Verify chained actionAfter() calls, that is, that a second + // actionAfter() resets the timer established by the first + // actionAfter(). + filter.actionAfter(20, + boost::bind(&Listener::call, boost::ref(listener1), LLSD("timeout"))); + // Since our TestEventTimeout class isn't actually manipulating time + // (quantities of seconds), only a bool "elapsed" flag, sense that by + // forcing the flag between actionAfter() calls. + filter.forceTimeout(); + // Pumping mainloop here would result in a timeout (as we'll verify + // below). This state simulates a ticking timer that has not yet timed + // out. But now, before a mainloop event lets 'filter' recognize + // timeout on the previous actionAfter() call, pretend we're pushing + // that timeout farther into the future. + filter.actionAfter(20, + boost::bind(&Listener::call, boost::ref(listener1), LLSD("timeout"))); + // Look ma, no timeout! + mainloop.post(17); + check_listener("no timeout 3", listener1, LLSD(0)); + // Now let the updated actionAfter() timer expire. + filter.forceTimeout(); + // Notice the timeout. + mainloop.post(17); + check_listener("timeout", listener1, LLSD("timeout")); + // Timing out cancels the timer. Verify that. + listener1.reset(0); + filter.forceTimeout(); + mainloop.post(17); + check_listener("no timeout 4", listener1, LLSD(0)); + // Reset the timer and then cancel() it. + filter.actionAfter(20, + boost::bind(&Listener::call, boost::ref(listener1), LLSD("timeout"))); + // neither expired nor satisified + mainloop.post(17); + check_listener("no timeout 5", listener1, LLSD(0)); + // cancel + filter.cancel(); + // timeout! + filter.forceTimeout(); + mainloop.post(17); + check_listener("no timeout 6", listener1, LLSD(0)); + } + + template<> template<> + void filter_object::test<3>() + { + set_test_name("LLEventTimeout::eventAfter()"); + LLEventPump& driver(pumps.obtain("driver")); + TestEventTimeout filter(driver); + listener0.reset(0); + LLTempBoundListener temp1( + listener0.listenTo(filter)); + filter.eventAfter(20, LLSD("timeout")); + // Okay, (fake) timer is ticking. 'filter' can only sense the timer + // when we pump mainloop. Do that right now to take the logic path + // before either the anticipated event arrives or the timer expires. + mainloop.post(17); + check_listener("no timeout 1", listener0, LLSD(0)); + // Expected event arrives... + driver.post(1); + check_listener("event passed thru", listener0, LLSD(1)); + // Should have canceled the timer. Verify that by asserting that the + // time has expired, then pumping mainloop again. + filter.forceTimeout(); + mainloop.post(17); + check_listener("no timeout 2", listener0, LLSD(1)); + // Set timer again. + filter.eventAfter(20, LLSD("timeout")); + // Now let the timer expire. + filter.forceTimeout(); + // Notice the timeout. + mainloop.post(17); + check_listener("timeout", listener0, LLSD("timeout")); + // Timing out cancels the timer. Verify that. + listener0.reset(0); + filter.forceTimeout(); + mainloop.post(17); + check_listener("no timeout 3", listener0, LLSD(0)); + } + + template<> template<> + void filter_object::test<4>() + { + set_test_name("LLEventTimeout::errorAfter()"); + WrapLL_ERRS capture; + LLEventPump& driver(pumps.obtain("driver")); + TestEventTimeout filter(driver); + listener0.reset(0); + LLTempBoundListener temp1( + listener0.listenTo(filter)); + filter.errorAfter(20, "timeout"); + // Okay, (fake) timer is ticking. 'filter' can only sense the timer + // when we pump mainloop. Do that right now to take the logic path + // before either the anticipated event arrives or the timer expires. + mainloop.post(17); + check_listener("no timeout 1", listener0, LLSD(0)); + // Expected event arrives... + driver.post(1); + check_listener("event passed thru", listener0, LLSD(1)); + // Should have canceled the timer. Verify that by asserting that the + // time has expired, then pumping mainloop again. + filter.forceTimeout(); + mainloop.post(17); + check_listener("no timeout 2", listener0, LLSD(1)); + // Set timer again. + filter.errorAfter(20, "timeout"); + // Now let the timer expire. + filter.forceTimeout(); + // Notice the timeout. + std::string threw; + try + { + mainloop.post(17); + } + catch (const WrapLL_ERRS::FatalException& e) + { + threw = e.what(); + } + ensure_contains("errorAfter() timeout exception", threw, "timeout"); + // Timing out cancels the timer. Verify that. + listener0.reset(0); + filter.forceTimeout(); + mainloop.post(17); + check_listener("no timeout 3", listener0, LLSD(0)); + } +} // namespace tut + +/***************************************************************************** +* Link dependencies +*****************************************************************************/ +#include "llsdutil.cpp" diff --git a/indra/llcommon/tests/llframetimer_test.cpp b/indra/llcommon/tests/llframetimer_test.cpp new file mode 100644 index 0000000000..1d047e41f8 --- /dev/null +++ b/indra/llcommon/tests/llframetimer_test.cpp @@ -0,0 +1,118 @@ +/** + * @file lltiming_test.cpp + * @date 2006-07-23 + * @brief Tests the timers. + * + * $LicenseInfo:firstyear=2006&license=viewergpl$ + * + * Copyright (c) 2006-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "../llframetimer.h" +#include "../llsd.h" + +#include "../test/lltut.h" + +namespace tut +{ + struct frametimer_test + { + frametimer_test() + { + LLFrameTimer::updateFrameTime(); + } + }; + typedef test_group<frametimer_test> frametimer_group_t; + typedef frametimer_group_t::object frametimer_object_t; + tut::frametimer_group_t frametimer_instance("frametimer"); + + template<> template<> + void frametimer_object_t::test<1>() + { + F64 seconds_since_epoch = LLFrameTimer::getTotalSeconds(); + LLFrameTimer timer; + timer.setExpiryAt(seconds_since_epoch); + F64 expires_at = timer.expiresAt(); + ensure_distance( + "set expiry matches get expiry", + expires_at, + seconds_since_epoch, + 0.001); + } + + template<> template<> + void frametimer_object_t::test<2>() + { + F64 seconds_since_epoch = LLFrameTimer::getTotalSeconds(); + seconds_since_epoch += 10.0; + LLFrameTimer timer; + timer.setExpiryAt(seconds_since_epoch); + F64 expires_at = timer.expiresAt(); + ensure_distance( + "set expiry matches get expiry 1", + expires_at, + seconds_since_epoch, + 0.001); + seconds_since_epoch += 10.0; + timer.setExpiryAt(seconds_since_epoch); + expires_at = timer.expiresAt(); + ensure_distance( + "set expiry matches get expiry 2", + expires_at, + seconds_since_epoch, + 0.001); + } + template<> template<> + void frametimer_object_t::test<3>() + { + F64 seconds_since_epoch = LLFrameTimer::getTotalSeconds(); + seconds_since_epoch += 2.0; + LLFrameTimer timer; + timer.setExpiryAt(seconds_since_epoch); + ensure("timer not expired on create", !timer.hasExpired()); + int ii; + for(ii = 0; ii < 10; ++ii) + { + ms_sleep(150); + LLFrameTimer::updateFrameTime(); + } + ensure("timer not expired after a bit", !timer.hasExpired()); + for(ii = 0; ii < 10; ++ii) + { + ms_sleep(100); + LLFrameTimer::updateFrameTime(); + } + ensure("timer expired", timer.hasExpired()); + } +/* + template<> template<> + void frametimer_object_t::test<4>() + { + } +*/ +} diff --git a/indra/llcommon/tests/llrand_test.cpp b/indra/llcommon/tests/llrand_test.cpp index e5100e51dc..1f178d6fc9 100644 --- a/indra/llcommon/tests/llrand_test.cpp +++ b/indra/llcommon/tests/llrand_test.cpp @@ -1,5 +1,5 @@ /** - * @file llrandom_tut.cpp + * @file llrandom_test.cpp * @author Phoenix * @date 2007-01-25 * diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 662b599c55..6ab48ec34a 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -1,5 +1,5 @@ /** - * @file llsdserialize_tut.cpp + * @file llsdserialize_test.cpp * @date 2006-04 * @brief LLSDSerialize unit tests * @@ -48,6 +48,18 @@ typedef U32 uint32_t; #endif +std::vector<U8> string_to_vector(std::string str) +{ + // bc LLSD can't... + size_t len = (size_t)str.length(); + std::vector<U8> v(len); + for (size_t i = 0; i < len ; i++) + { + v[i] = str[i]; + } + return v; +} + namespace tut { struct sd_xml_data @@ -107,7 +119,16 @@ namespace tut expected = "<llsd><date>2006-04-24T16:11:33Z</date></llsd>\n"; xml_test("date", expected); - // *FIX: test binary + // Generated by: echo -n 'hello' | openssl enc -e -base64 + std::vector<U8> hello; + hello.push_back('h'); + hello.push_back('e'); + hello.push_back('l'); + hello.push_back('l'); + hello.push_back('o'); + mSD = hello; + expected = "<llsd><binary encoding=\"base64\">aGVsbG8=</binary></llsd>\n"; + xml_test("binary", expected); } template<> template<> @@ -199,6 +220,21 @@ namespace tut xml_test("2 element map", expected); } + template<> template<> + void sd_xml_object::test<6>() + { + // tests with binary + std::string expected; + + // Generated by: echo -n 'hello' | openssl enc -e -base64 + mSD = string_to_vector("hello"); + expected = "<llsd><binary encoding=\"base64\">aGVsbG8=</binary></llsd>\n"; + xml_test("binary", expected); + + mSD = string_to_vector("6|6|asdfhappybox|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|00000000-0000-0000-0000-000000000000|7fffffff|7fffffff|0|0|82000|450fe394-2904-c9ad-214c-a07eb7feec29|(No Description)|0|10|0"); + expected = "<llsd><binary encoding=\"base64\">Nnw2fGFzZGZoYXBweWJveHw2MGU0NGVjNS0zMDVjLTQzYzItOWExOS1iNGI4OWIxYWUyYTZ8NjBlNDRlYzUtMzA1Yy00M2MyLTlhMTktYjRiODliMWFlMmE2fDYwZTQ0ZWM1LTMwNWMtNDNjMi05YTE5LWI0Yjg5YjFhZTJhNnwwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDB8N2ZmZmZmZmZ8N2ZmZmZmZmZ8MHwwfDgyMDAwfDQ1MGZlMzk0LTI5MDQtYzlhZC0yMTRjLWEwN2ViN2ZlZWMyOXwoTm8gRGVzY3JpcHRpb24pfDB8MTB8MA==</binary></llsd>\n"; + xml_test("binary", expected); + } class TestLLSDSerializeData { @@ -637,6 +673,42 @@ namespace tut v.size() + 1); } + template<> template<> + void TestLLSDXMLParsingObject::test<4>() + { + // test handling of binary object in XML + std::string xml; + LLSD expected; + + // Generated by: echo -n 'hello' | openssl enc -e -base64 + expected = string_to_vector("hello"); + xml = "<llsd><binary encoding=\"base64\">aGVsbG8=</binary></llsd>\n"; + ensureParse( + "the word 'hello' packed in binary encoded base64", + xml, + expected, + 1); + + expected = string_to_vector("6|6|asdfhappybox|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|00000000-0000-0000-0000-000000000000|7fffffff|7fffffff|0|0|82000|450fe394-2904-c9ad-214c-a07eb7feec29|(No Description)|0|10|0"); + xml = "<llsd><binary encoding=\"base64\">Nnw2fGFzZGZoYXBweWJveHw2MGU0NGVjNS0zMDVjLTQzYzItOWExOS1iNGI4OWIxYWUyYTZ8NjBlNDRlYzUtMzA1Yy00M2MyLTlhMTktYjRiODliMWFlMmE2fDYwZTQ0ZWM1LTMwNWMtNDNjMi05YTE5LWI0Yjg5YjFhZTJhNnwwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDB8N2ZmZmZmZmZ8N2ZmZmZmZmZ8MHwwfDgyMDAwfDQ1MGZlMzk0LTI5MDQtYzlhZC0yMTRjLWEwN2ViN2ZlZWMyOXwoTm8gRGVzY3JpcHRpb24pfDB8MTB8MA==</binary></llsd>\n"; + ensureParse( + "a common binary blob for object -> agent offline inv transfer", + xml, + expected, + 1); + + expected = string_to_vector("6|6|asdfhappybox|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|60e44ec5-305c-43c2-9a19-b4b89b1ae2a6|00000000-0000-0000-0000-000000000000|7fffffff|7fffffff|0|0|82000|450fe394-2904-c9ad-214c-a07eb7feec29|(No Description)|0|10|0"); + xml = "<llsd><binary encoding=\"base64\">Nnw2fGFzZGZoYXBweWJveHw2MGU0NGVjNS0zMDVjLTQzYzItOWExOS1iNGI4OWIxYWUyYTZ8NjBl\n"; + xml += "NDRlYzUtMzA1Yy00M2MyLTlhMTktYjRiODliMWFlMmE2fDYwZTQ0ZWM1LTMwNWMtNDNjMi05YTE5\n"; + xml += "LWI0Yjg5YjFhZTJhNnwwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDB8N2ZmZmZm\n"; + xml += "ZmZ8N2ZmZmZmZmZ8MHwwfDgyMDAwfDQ1MGZlMzk0LTI5MDQtYzlhZC0yMTRjLWEwN2ViN2ZlZWMy\n"; + xml += "OXwoTm8gRGVzY3JpcHRpb24pfDB8MTB8MA==</binary></llsd>\n"; + ensureParse( + "a common binary blob for object -> agent offline inv transfer", + xml, + expected, + 1); + } /* TODO: test XML parsing diff --git a/indra/llcommon/tests/llstring_test.cpp b/indra/llcommon/tests/llstring_test.cpp index 6a2ebc61f5..beba55416a 100644 --- a/indra/llcommon/tests/llstring_test.cpp +++ b/indra/llcommon/tests/llstring_test.cpp @@ -32,6 +32,7 @@ * $/LicenseInfo$ */ +#include "linden_common.h" #include "../test/lltut.h" #include "../llstring.h" diff --git a/indra/llcommon/tests/lltreeiterators_test.cpp b/indra/llcommon/tests/lltreeiterators_test.cpp new file mode 100644 index 0000000000..d6d9f68110 --- /dev/null +++ b/indra/llcommon/tests/lltreeiterators_test.cpp @@ -0,0 +1,1222 @@ +/** + * @file lltreeiterators.cpp + * @author Nat Goodspeed + * @date 2008-08-20 + * @brief Test of lltreeiterators.h + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" + +#if LL_WINDOWS +#pragma warning (disable : 4180) // qualifier applied to function type has no meaning; ignored +#endif + +// STL headers +// std headers +#include <iostream> +#include <sstream> +#include <string> +// external library headers +#include <boost/bind.hpp> +#include <boost/range/iterator_range.hpp> +#include <boost/foreach.hpp> + +// associated header +#include "../lltreeiterators.h" +#include "../llpointer.h" + +#include "../test/lltut.h" + +/***************************************************************************** +* tut test group +*****************************************************************************/ +namespace tut +{ + struct iter_data + { + }; + typedef test_group<iter_data> iter_group; + typedef iter_group::object iter_object; + tut::iter_group ig("lltreeiterators"); +} // namespace tut + +/***************************************************************************** +* boost::get_pointer() specialization for LLPointer<> +*****************************************************************************/ +// This specialization of boost::get_pointer() undoubtedly belongs in +// llmemory.h. It's used by boost::bind() so that you can pass an +// LLPointer<Foo> as well as a Foo* to a functor such as +// boost::bind(&Foo::method, _1). +//namespace boost +//{ + template <class NODE> + NODE* get_pointer(const LLPointer<NODE>& ptr) { return ptr.get(); } +//}; + +/***************************************************************************** +* ScopeLabel +*****************************************************************************/ +class ScopeLabel +{ +public: + ScopeLabel(const std::string& label): mLabel(label) + { + std::cout << "Entering " << mLabel << '\n'; + } + ~ScopeLabel() + { + std::cout << "Leaving " << mLabel << '\n'; + } +private: + std::string mLabel; +}; + +/***************************************************************************** +* Cleanup +*****************************************************************************/ +// Yes, we realize this is redundant with auto_ptr and LLPointer and all +// kinds of better mechanisms. But in this particular source file, we need to +// test nodes managed with plain old dumb pointers as well as nodes managed +// with LLPointer, so we introduce this mechanism. +// +// In the general case, when we declare a Cleanup for some pointer, delete the +// pointer when the Cleanup goes out of scope. +template <typename PTRTYPE> +struct Cleanup +{ + Cleanup(const PTRTYPE& ptr): mPtr(ptr) {} + ~Cleanup() + { + delete mPtr; + } + PTRTYPE mPtr; +}; + +// But when the pointer is an LLPointer<something>, Cleanup is a no-op: +// LLPointer will handle the cleanup automagically. +template <typename NODE> +struct Cleanup< LLPointer<NODE> > +{ + Cleanup(const LLPointer<NODE>& ptr) {} + ~Cleanup() {} +}; + +/***************************************************************************** +* Expected +*****************************************************************************/ +// Expected is a base class used to capture the expected results -- a sequence +// of string node names -- from one of our traversals of this example data. +// Its subclasses initialize it with a pair of string iterators. It's not +// strictly necessary to customize Expected to model Boost.Range, it's just +// convenient. +struct Expected +{ + template <typename ITER> + Expected(ITER begin, ITER end): + strings(begin, end) + {} + /*------ The following are to make Expected work with Boost.Range ------*/ + typedef std::vector<std::string> container_type; + typedef container_type::iterator iterator; + typedef container_type::const_iterator const_iterator; + typedef container_type::size_type size_type; + container_type strings; + iterator begin() { return strings.begin(); } + iterator end() { return strings.end(); } + size_type size() { return strings.size(); } + const_iterator begin() const { return strings.begin(); } + const_iterator end() const { return strings.end(); } +}; + +// We have a couple of generic Expected template subclasses. This list of +// strings is used for the "else" case when all specializations fail. +const char* bad_strings[] = { "FAIL" }; + +/***************************************************************************** +* verify() +*****************************************************************************/ +// test function: given (an object modeling) a Boost.Range of tree nodes, +// compare the sequence of visited node names with a range of expected name +// strings. Report success both with std::cout output and a bool return. The +// string desc parameter is to identify the different tests. +template <typename NODERANGE, typename STRINGRANGE> +bool verify(const std::string& desc, NODERANGE noderange, STRINGRANGE expected) +{ + typename boost::range_iterator<NODERANGE>::type + nri = boost::begin(noderange), + nrend = boost::end(noderange); + typename boost::range_iterator<STRINGRANGE>::type + sri = boost::begin(expected), + srend = boost::end(expected); + // We choose to loop over both sequences explicitly rather than using + // std::equal() or std::lexicographical_compare(). The latter tells you + // whether one sequence is *less* than the other -- it doesn't tell you + // equality. std::equal() needs you to verify the sequence lengths ahead + // of time. Anyway, comparing explicitly allows us to report much more + // information about any sequence mismatch. + for ( ; nri != nrend && sri != srend; ++nri, ++sri) + { + if ((*nri)->name() != *sri) + { + std::cout << desc << " mismatch: " + << "expected " << *sri << ", got " << (*nri)->name() << "\n"; + return false; + } + } + if (nri != nrend) + { + std::cout << desc << " produced too many items:\n"; + for ( ; nri != nrend; ++nri) + { + std::cout << " " << (*nri)->name() << '\n'; + } + return false; + } + if (sri != srend) + { + std::cout << desc << " produced too few items, omitting:\n"; + for ( ; sri != srend; ++sri) + { + std::cout << " " << *sri << '\n'; + } + return false; + } +// std::cout << desc << " test passed\n"; + return true; +} + +/***************************************************************************** +* PlainNode: LLLinkIter, non-refcounted +*****************************************************************************/ +class PlainNode +{ +public: + PlainNode(const std::string& name, PlainNode* next=NULL): + mName(name), + mNext(next) + {} + ~PlainNode() + { + delete mNext; + } + std::string name() const { return mName; } + PlainNode* next() const { return mNext; } +public: // if this were 'private', couldn't bind mNext + PlainNode* mNext; +private: + std::string mName; +}; + +namespace tut +{ + template<> template<> + void iter_object::test<1>() + { +// set_test_name("LLLinkedIter -- non-refcounted class"); + PlainNode* last(new PlainNode("c")); + PlainNode* second(new PlainNode("b", last)); + PlainNode* first(new PlainNode("a", second)); + Cleanup<PlainNode*> cleanup(first); + static const char* cseq[] = { "a", "b", "c" }; + Expected seq(boost::begin(cseq), boost::end(cseq)); + std::string desc1("Iterate by public link member"); +// std::cout << desc1 << ":\n"; + // Try instantiating an iterator with NULL. This test is less about + // "did we iterate once?" than "did we avoid blowing up?" + for (LLLinkedIter<PlainNode> pni(NULL, boost::bind(&PlainNode::mNext, _1)), end; + pni != end; ++pni) + { +// std::cout << (*pni)->name() << '\n'; + ensure("LLLinkedIter<PlainNode>(NULL)", false); + } + ensure(desc1, + verify(desc1, + boost::make_iterator_range(LLLinkedIter<PlainNode>(first, + boost::bind(&PlainNode::mNext, _1)), + LLLinkedIter<PlainNode>()), + seq)); + std::string desc2("Iterate by next() method"); +// std::cout << desc2 << ":\n"; +// for (LLLinkedIter<PlainNode> pni(first, boost::bind(&PlainNode::next, _1)); ! (pni == end); ++pni) +// std::cout << (**pni).name() << '\n'; + ensure(desc2, + verify(desc2, + boost::make_iterator_range(LLLinkedIter<PlainNode>(first, + boost::bind(&PlainNode::next, _1)), + LLLinkedIter<PlainNode>()), + seq)); + { +// LLLinkedIter<PlainNode> pni(first, boost::bind(&PlainNode::next, _1)); +// std::cout << "First is " << (*pni++)->name() << '\n'; +// std::cout << "Second is " << (*pni )->name() << '\n'; + } + { + LLLinkedIter<PlainNode> pni(first, boost::bind(&PlainNode::next, _1)); + ensure_equals("first", (*pni++)->name(), "a"); + ensure_equals("second", (*pni )->name(), "b"); + } + } +} // tut + +/***************************************************************************** +* RCNode: LLLinkIter, refcounted +*****************************************************************************/ +class RCNode; +typedef LLPointer<RCNode> RCNodePtr; + +class RCNode: public LLRefCount +{ +public: + RCNode(const std::string& name, const RCNodePtr& next=RCNodePtr()): + mName(name), + mNext(next) + { +// std::cout << "New RCNode(" << mName << ")\n"; + } + RCNode(const RCNode& that): + mName(that.mName), + mNext(that.mNext) + { +// std::cout << "Copy RCNode(" << mName << ")\n"; + } + virtual ~RCNode(); + std::string name() const { return mName; } + RCNodePtr next() const { return mNext; } +public: // if this were 'private', couldn't bind mNext + RCNodePtr mNext; +private: + std::string mName; +}; + +std::ostream& operator<<(std::ostream& out, const RCNode& node) +{ + out << "RCNode(" << node.name() << ')'; + return out; +} + +// This string contains the node name of the last RCNode destroyed. We use it +// to validate that LLLinkedIter<RCNode> in fact contains LLPointer<RCNode>, +// and that therefore an outstanding LLLinkedIter to an instance of a +// refcounted class suffices to keep that instance alive. +std::string last_RCNode_destroyed; + +RCNode::~RCNode() +{ +// std::cout << "Kill " << *this << "\n"; + last_RCNode_destroyed = mName; +} + +namespace tut +{ + template<> template<> + void iter_object::test<2>() + { +// set_test_name("LLLinkedIter -- refcounted class"); + LLLinkedIter<RCNode> rcni, end2; + { +// ScopeLabel label("inner scope"); + RCNodePtr head(new RCNode("x", new RCNode("y", new RCNode("z")))); +// for (rcni = LLLinkedIter<RCNode>(head, boost::bind(&RCNode::mNext, _1)); rcni != end2; ++rcni) +// std::cout << **rcni << '\n'; + rcni = LLLinkedIter<RCNode>(head, boost::bind(&RCNode::next, _1)); + } +// std::cout << "Now the LLLinkedIter<RCNode> is the only remaining reference to RCNode chain\n"; + ensure_equals(last_RCNode_destroyed, ""); + ensure(rcni != end2); + ensure_equals((*rcni)->name(), "x"); + ++rcni; + ensure_equals(last_RCNode_destroyed, "x"); + ensure(rcni != end2); + ensure_equals((*rcni)->name(), "y"); + ++rcni; + ensure_equals(last_RCNode_destroyed, "y"); + ensure(rcni != end2); + ensure_equals((*rcni)->name(), "z"); + ++rcni; + ensure_equals(last_RCNode_destroyed, "z"); + ensure(rcni == end2); + } +} + +/***************************************************************************** +* TreeNode +*****************************************************************************/ +class TreeNode; +typedef LLPointer<TreeNode> TreeNodePtr; + +/** + * TreeNode represents a refcounted tree-node class that hasn't (yet) been + * modified to incorporate LLTreeIter methods. This illustrates how you can + * use tree iterators either standalone, or with free functions. + */ +class TreeNode: public LLRefCount +{ +public: + typedef std::vector<TreeNodePtr> list_type; + typedef list_type::const_iterator child_iterator; + + // To avoid cycles, use a "weak" raw pointer for the parent link + TreeNode(const std::string& name, TreeNode* parent=0): + mParent(parent), + mName(name) + {} + TreeNodePtr newChild(const std::string& name) + { + TreeNodePtr child(new TreeNode(name, this)); + mChildren.push_back(child); + return child; + } + std::string name() const { return mName; } + TreeNodePtr getParent() const { return mParent; } + child_iterator child_begin() const { return mChildren.begin(); } + child_iterator child_end() const { return mChildren.end(); } +private: + std::string mName; + // To avoid cycles, use a "weak" raw pointer for the parent link + TreeNode* mParent; + list_type mChildren; +}; + +/** + * This is an example of a helper function to facilitate iterating from a + * TreeNode up to the root or down from the root (see LLTreeIter::RootIter). + * + * Example: + * @code + * BOOST_FOREACH(TreeNodePtr node, getRootRange<LLTreeIter::UP>(somenode)) + * { + * std::cout << node->name() << '\n'; + * } + * @endcode + */ +template <LLTreeIter::RootIter DISCRIM> +boost::iterator_range< LLTreeRootIter<DISCRIM, TreeNode> > +getRootRange(const TreeNodePtr& node) +{ + typedef LLTreeRootIter<DISCRIM, TreeNode> iter_type; + typedef boost::iterator_range<iter_type> range_type; + return range_type(iter_type(node, boost::bind(&TreeNode::getParent, _1)), + iter_type()); +} + +/** + * This is an example of a helper function to facilitate walking a given + * TreeNode's subtree in any supported order (see LLTreeIter::WalkIter). + * + * Example: + * @code + * BOOST_FOREACH(TreeNodePtr node, getWalkRange<LLTreeIter::DFS_PRE>(root)) + * { + * std::cout << node->name() << '\n'; + * } + * @endcode + */ +template <LLTreeIter::WalkIter DISCRIM> +boost::iterator_range< LLTreeWalkIter<DISCRIM, TreeNode, TreeNode::child_iterator> > +getWalkRange(const TreeNodePtr& node) +{ + typedef LLTreeWalkIter<DISCRIM, TreeNode, TreeNode::child_iterator> iter_type; + typedef boost::iterator_range<iter_type> range_type; + return range_type(iter_type(node, + boost::bind(&TreeNode::child_begin, _1), + boost::bind(&TreeNode::child_end, _1)), + iter_type()); +} + +/***************************************************************************** +* EnhancedTreeNode +*****************************************************************************/ +class EnhancedTreeNode; +typedef LLPointer<EnhancedTreeNode> EnhancedTreeNodePtr; + +/** + * More typically, you enhance the tree-node class itself with template + * methods like the above. This EnhancedTreeNode class illustrates the + * technique. Normally, of course, you'd simply add these methods to TreeNode; + * we put them in a separate class to preserve the undecorated TreeNode class + * to illustrate (and test) the use of plain tree iterators and standalone + * helper functions. + * + * We originally implemented EnhancedTreeNode as a subclass of TreeNode -- but + * because TreeNode stores and manipulates TreeNodePtrs and TreeNode*s, + * reusing its methods required so much ugly downcast logic that we gave up + * and restated the whole class. Bear in mind that logically these aren't two + * separate classes; logically they're two snapshots of the @em same class at + * different moments in time. + */ +class EnhancedTreeNode: public LLRefCount +{ +public: + /*-------------- The following is restated from TreeNode ---------------*/ + typedef std::vector<EnhancedTreeNodePtr> list_type; + typedef list_type::const_iterator child_iterator; + + // To avoid cycles, use a "weak" raw pointer for the parent link + EnhancedTreeNode(const std::string& name, EnhancedTreeNode* parent=0): + mParent(parent), + mName(name) + {} + EnhancedTreeNodePtr newChild(const std::string& name) + { + EnhancedTreeNodePtr child(new EnhancedTreeNode(name, this)); + mChildren.push_back(child); + return child; + } + std::string name() const { return mName; } + EnhancedTreeNodePtr getParent() const { return mParent; } + child_iterator child_begin() const { return mChildren.begin(); } + child_iterator child_end() const { return mChildren.end(); } + +private: + std::string mName; + // To avoid cycles, use a "weak" raw pointer for the parent link + EnhancedTreeNode* mParent; + list_type mChildren; +public: + /*----- End of TreeNode; what follows is new with EnhancedTreeNode -----*/ + + /** + * Because the type of the iterator range returned by getRootRange() + * depends on the discriminator enum value, instead of a simple typedef we + * use a templated struct. Example usage: + * + * @code + * for (EnhancedTreeNode::root_range<LLTreeIter::UP>::type range = + * somenode->getRootRange<LLTreeIter::UP>(); + * range.first != range.second; ++range.first) + * { + * std::cout << (*range.first)->name() << '\n'; + * } + * @endcode + */ + template <LLTreeIter::RootIter DISCRIM> + struct root_range + { + typedef boost::iterator_range< LLTreeRootIter<DISCRIM, EnhancedTreeNode> > type; + }; + + /** + * Helper method for walking up to (or down from) the tree root. See + * LLTreeIter::RootIter. + * + * Example usage: + * @code + * BOOST_FOREACH(EnhancedTreeNodePtr node, somenode->getRootRange<LLTreeIter::UP>()) + * { + * std::cout << node->name() << '\n'; + * } + * @endcode + */ + template <LLTreeIter::RootIter DISCRIM> + typename root_range<DISCRIM>::type getRootRange() const + { + typedef typename root_range<DISCRIM>::type range_type; + typedef typename range_type::iterator iter_type; + return range_type(iter_type(const_cast<EnhancedTreeNode*>(this), + boost::bind(&EnhancedTreeNode::getParent, _1)), + iter_type()); + } + + /** + * Because the type of the iterator range returned by getWalkRange() + * depends on the discriminator enum value, instead of a simple typedef we + * use a templated stuct. Example usage: + * + * @code + * for (EnhancedTreeNode::walk_range<LLTreeIter::DFS_PRE>::type range = + * somenode->getWalkRange<LLTreeIter::DFS_PRE>(); + * range.first != range.second; ++range.first) + * { + * std::cout << (*range.first)->name() << '\n'; + * } + * @endcode + */ + template <LLTreeIter::WalkIter DISCRIM> + struct walk_range + { + typedef boost::iterator_range< LLTreeWalkIter<DISCRIM, + EnhancedTreeNode, + EnhancedTreeNode::child_iterator> > type; + }; + + /** + * Helper method for walking a given node's subtree in any supported + * order (see LLTreeIter::WalkIter). + * + * Example usage: + * @code + * BOOST_FOREACH(EnhancedTreeNodePtr node, somenode->getWalkRange<LLTreeIter::DFS_PRE>()) + * { + * std::cout << node->name() << '\n'; + * } + * @endcode + */ + template <LLTreeIter::WalkIter DISCRIM> + typename walk_range<DISCRIM>::type getWalkRange() const + { + typedef typename walk_range<DISCRIM>::type range_type; + typedef typename range_type::iterator iter_type; + return range_type(iter_type(const_cast<EnhancedTreeNode*>(this), + boost::bind(&EnhancedTreeNode::child_begin, _1), + boost::bind(&EnhancedTreeNode::child_end, _1)), + iter_type()); + } +}; + +/***************************************************************************** +* PlainTree +*****************************************************************************/ +struct PlainTree +{ + PlainTree(const std::string& name, PlainTree* parent=0): + mName(name), + mParent(parent), + mNextSibling(0), + mFirstChild(0) + { + mLastChildLink = &mFirstChild; + } + ~PlainTree() + { + delete mNextSibling; + delete mFirstChild; + } + PlainTree* newChild(const std::string& name) + { + PlainTree* child(new PlainTree(name, this)); + *mLastChildLink = child; + mLastChildLink = &child->mNextSibling; + return child; + } + std::string name() const { return mName; } + + std::string mName; + PlainTree* mParent; + PlainTree* mNextSibling; + PlainTree* mFirstChild; + PlainTree** mLastChildLink; +}; + +// This "classic" tree tracks each node's children with a linked list anchored +// at the parent's mFirstChild and linked through each child's mNextSibling. +// LLTreeDFSIter<> and LLTreeBFSIter<> need functors to return begin()/end() +// iterators over a given node's children. But because this tree's children +// aren't stored in an STL container, we can't just export that container's +// begin()/end(). Instead we'll use LLLinkedIter<> to view the hand-maintained +// linked list as an iterator range. The straightforward way to do that would +// be to add child_begin() and child_end() methods. But let's say (for the +// sake of argument) that this struct is so venerable we don't dare modify it +// even to add new methods. Well, we can use free functions (or functors) too. +LLLinkedIter<PlainTree> PlainTree_child_begin(PlainTree* node) +{ + return LLLinkedIter<PlainTree>(node->mFirstChild, boost::bind(&PlainTree::mNextSibling, _1)); +} + +LLLinkedIter<PlainTree> PlainTree_child_end(PlainTree* node) +{ + return LLLinkedIter<PlainTree>(); +} + +/** + * This is an example of a helper function to facilitate iterating from a + * PlainTree up to the root or down from the root (see LLTreeIter::RootIter). + * Note that we're simply overloading the same getRootRange() helper function + * name we used for TreeNode. + * + * Example: + * @code + * BOOST_FOREACH(PlainTree* node, getRootRange<LLTreeIter::UP>(somenode)) + * { + * std::cout << node->name() << '\n'; + * } + * @endcode + */ +template <LLTreeIter::RootIter DISCRIM> +boost::iterator_range< LLTreeRootIter<DISCRIM, PlainTree> > +getRootRange(PlainTree* node) +{ + typedef LLTreeRootIter<DISCRIM, PlainTree> iter_type; + typedef boost::iterator_range<iter_type> range_type; + return range_type(iter_type(node, boost::bind(&PlainTree::mParent, _1)), + iter_type()); +} + +/** + * This is an example of a helper function to facilitate walking a given + * PlainTree's subtree in any supported order (see LLTreeIter::WalkIter). Note + * that we're simply overloading the same getWalkRange() helper function name + * we used for TreeNode. + * + * Example: + * @code + * BOOST_FOREACH(PlainTree* node, getWalkRange<LLTreeIter::DFS_PRE>(root)) + * { + * std::cout << node->name() << '\n'; + * } + * @endcode + */ +template <LLTreeIter::WalkIter DISCRIM> +boost::iterator_range< LLTreeWalkIter<DISCRIM, PlainTree, LLLinkedIter<PlainTree> > > +getWalkRange(PlainTree* node) +{ + typedef LLTreeWalkIter<DISCRIM, PlainTree, LLLinkedIter<PlainTree> > iter_type; + typedef boost::iterator_range<iter_type> range_type; + return range_type(iter_type(node, + PlainTree_child_begin, + PlainTree_child_end), + iter_type()); +} + +// We could go through the exercise of writing EnhancedPlainTree containing +// root_range, getRootRange(), walk_range and getWalkRange() members -- but we +// won't. See EnhancedTreeNode for examples. + +/***************************************************************************** +* Generic tree test data +*****************************************************************************/ +template <class NODE> +typename LLPtrTo<NODE>::type example_tree() +{ + typedef typename LLPtrTo<NODE>::type NodePtr; + NodePtr root(new NODE("root")); + NodePtr A(root->newChild("A")); + NodePtr A1(A->newChild("A1")); +/* NodePtr A1a*/(A1->newChild("A1a")); +/* NodePtr A1b*/(A1->newChild("A1b")); +/* NodePtr A1c*/(A1->newChild("A1c")); + NodePtr A2(A->newChild("A2")); +/* NodePtr A2a*/(A2->newChild("A2a")); +/* NodePtr A2b*/(A2->newChild("A2b")); +/* NodePtr A2c*/(A2->newChild("A2c")); + NodePtr A3(A->newChild("A3")); +/* NodePtr A3a*/(A3->newChild("A3a")); +/* NodePtr A3b*/(A3->newChild("A3b")); +/* NodePtr A3c*/(A3->newChild("A3c")); + NodePtr B(root->newChild("B")); + NodePtr B1(B->newChild("B1")); +/* NodePtr B1a*/(B1->newChild("B1a")); +/* NodePtr B1b*/(B1->newChild("B1b")); +/* NodePtr B1c*/(B1->newChild("B1c")); + NodePtr B2(B->newChild("B2")); +/* NodePtr B2a*/(B2->newChild("B2a")); +/* NodePtr B2b*/(B2->newChild("B2b")); +/* NodePtr B2c*/(B2->newChild("B2c")); + NodePtr B3(B->newChild("B3")); +/* NodePtr B3a*/(B3->newChild("B3a")); +/* NodePtr B3b*/(B3->newChild("B3b")); +/* NodePtr B3c*/(B3->newChild("B3c")); + NodePtr C(root->newChild("C")); + NodePtr C1(C->newChild("C1")); +/* NodePtr C1a*/(C1->newChild("C1a")); +/* NodePtr C1b*/(C1->newChild("C1b")); +/* NodePtr C1c*/(C1->newChild("C1c")); + NodePtr C2(C->newChild("C2")); +/* NodePtr C2a*/(C2->newChild("C2a")); +/* NodePtr C2b*/(C2->newChild("C2b")); +/* NodePtr C2c*/(C2->newChild("C2c")); + NodePtr C3(C->newChild("C3")); +/* NodePtr C3a*/(C3->newChild("C3a")); +/* NodePtr C3b*/(C3->newChild("C3b")); +/* NodePtr C3c*/(C3->newChild("C3c")); + return root; +} + +// WalkExpected<WalkIter> is the list of string node names we expect from a +// WalkIter traversal of our example_tree() data. +template <LLTreeIter::WalkIter DISCRIM> +struct WalkExpected: public Expected +{ + // Initialize with bad_strings: we don't expect to use this generic case, + // only the specializations. Note that for a classic C-style array we must + // pass a pair of iterators rather than extracting boost::begin() and + // boost::end() within the target constructor: a template ctor accepts + // these classic C-style arrays as char** rather than char*[length]. Oh well. + WalkExpected(): Expected(boost::begin(bad_strings), boost::end(bad_strings)) {} +}; + +// list of string node names we expect from traversing example_tree() in +// DFS_PRE order +const char* dfs_pre_strings[] = +{ + "root", + "A", + "A1", + "A1a", + "A1b", + "A1c", + "A2", + "A2a", + "A2b", + "A2c", + "A3", + "A3a", + "A3b", + "A3c", + "B", + "B1", + "B1a", + "B1b", + "B1c", + "B2", + "B2a", + "B2b", + "B2c", + "B3", + "B3a", + "B3b", + "B3c", + "C", + "C1", + "C1a", + "C1b", + "C1c", + "C2", + "C2a", + "C2b", + "C2c", + "C3", + "C3a", + "C3b", + "C3c" +}; + +// specialize WalkExpected<DFS_PRE> with the expected strings +template <> +struct WalkExpected<LLTreeIter::DFS_PRE>: public Expected +{ + WalkExpected(): Expected(boost::begin(dfs_pre_strings), boost::end(dfs_pre_strings)) {} +}; + +// list of string node names we expect from traversing example_tree() in +// DFS_POST order +const char* dfs_post_strings[] = +{ + "A1a", + "A1b", + "A1c", + "A1", + "A2a", + "A2b", + "A2c", + "A2", + "A3a", + "A3b", + "A3c", + "A3", + "A", + "B1a", + "B1b", + "B1c", + "B1", + "B2a", + "B2b", + "B2c", + "B2", + "B3a", + "B3b", + "B3c", + "B3", + "B", + "C1a", + "C1b", + "C1c", + "C1", + "C2a", + "C2b", + "C2c", + "C2", + "C3a", + "C3b", + "C3c", + "C3", + "C", + "root" +}; + +// specialize WalkExpected<DFS_POST> with the expected strings +template <> +struct WalkExpected<LLTreeIter::DFS_POST>: public Expected +{ + WalkExpected(): Expected(boost::begin(dfs_post_strings), boost::end(dfs_post_strings)) {} +}; + +// list of string node names we expect from traversing example_tree() in BFS order +const char* bfs_strings[] = +{ + "root", + "A", + "B", + "C", + "A1", + "A2", + "A3", + "B1", + "B2", + "B3", + "C1", + "C2", + "C3", + "A1a", + "A1b", + "A1c", + "A2a", + "A2b", + "A2c", + "A3a", + "A3b", + "A3c", + "B1a", + "B1b", + "B1c", + "B2a", + "B2b", + "B2c", + "B3a", + "B3b", + "B3c", + "C1a", + "C1b", + "C1c", + "C2a", + "C2b", + "C2c", + "C3a", + "C3b", + "C3c" +}; + +// specialize WalkExpected<BFS> with the expected strings +template <> +struct WalkExpected<LLTreeIter::BFS>: public Expected +{ + WalkExpected(): Expected(boost::begin(bfs_strings), boost::end(bfs_strings)) {} +}; + +// extract a particular "arbitrary" node from the example_tree() data: the +// second (middle) node at each child level +template <class NODE, typename CHILDITER> +typename LLPtrTo<NODE>::type +get_B2b(const typename LLPtrTo<NODE>::type& root, + const boost::function<CHILDITER(const typename LLPtrTo<NODE>::type&)>& child_begin) +{ + typedef typename LLPtrTo<NODE>::type NodePtr; + CHILDITER Bi(child_begin(root)); + ++Bi; + NodePtr B(*Bi); + CHILDITER B2i(child_begin(B)); + ++B2i; + NodePtr B2(*B2i); + CHILDITER B2bi(child_begin(B2)); + ++B2bi; + NodePtr B2b(*B2bi); + return B2b; +} + +// RootExpected<RootIter> is the list of string node names we expect from a +// RootIter traversal of our example_tree() data. +template <LLTreeIter::RootIter DISCRIM> +struct RootExpected: public Expected +{ + // Initialize with bad_strings: we don't expect to use this generic case, + // only the specializations. + RootExpected(): Expected(boost::begin(bad_strings), boost::end(bad_strings)) {} +}; + +// list of string node names we expect from traversing UP from +// example_tree()'s B2b node +const char* up_from_B2b[] = +{ + "B2b", + "B2", + "B", + "root" +}; + +// specialize RootExpected<UP> with the expected strings +template <> +struct RootExpected<LLTreeIter::UP>: public Expected +{ + RootExpected(): Expected(boost::begin(up_from_B2b), boost::end(up_from_B2b)) {} +}; + +// list of string node names we expect from traversing DOWN to +// example_tree()'s B2b node +const char* down_to_B2b[] = +{ + "root", + "B", + "B2", + "B2b" +}; + +// specialize RootExpected<DOWN> with the expected strings +template <> +struct RootExpected<LLTreeIter::DOWN>: public Expected +{ + RootExpected(): Expected(boost::begin(down_to_B2b), boost::end(down_to_B2b)) {} +}; + +/***************************************************************************** +* Generic tree test functions +*****************************************************************************/ +template<LLTreeIter::RootIter DISCRIM, class NODE, typename PARENTFUNC> +bool LLTreeRootIter_test(const std::string& itername, const std::string& nodename, + const typename LLPtrTo<NODE>::type& node, + PARENTFUNC parentfunc) +{ + std::ostringstream desc; + desc << itername << '<' << nodename << "> from " << node->name(); + if (! verify(desc.str(), + boost::make_iterator_range(LLTreeRootIter<DISCRIM, NODE>(node, parentfunc), + LLTreeRootIter<DISCRIM, NODE>()), + RootExpected<DISCRIM>())) + return false; +// std::cout << desc.str() << '\n'; + // Try instantiating an iterator with NULL (that is, a default-constructed + // node pointer). This test is less about "did we iterate once?" than "did + // we avoid blowing up?" + for (LLTreeRootIter<DISCRIM, NODE> hri = LLTreeRootIter<DISCRIM, NODE>(typename LLPtrTo<NODE>::type(), parentfunc), hrend; + hri != hrend; /* ++hri */) // incrementing is moot, and MSVC complains + { +// std::cout << nodename << '(' << (*hri)->name() << ")\n"; + std::cout << itername << '<' << nodename << ">(NULL)\n"; + return false; + } + return true; +} + +template<class NODE, typename CHILDITER, typename PARENTFUNC, typename CHILDFUNC> +bool LLTreeUpIter_test(const std::string& nodename, PARENTFUNC parentfunc, CHILDFUNC childfunc) +{ + bool success = true; + typedef typename LLPtrTo<NODE>::type ptr_type; + ptr_type root(example_tree<NODE>()); + Cleanup<ptr_type> cleanup(root); + ptr_type B2b(get_B2b<NODE, CHILDITER>(root, childfunc)); + if (! LLTreeRootIter_test<LLTreeIter::UP, NODE>("LLTreeUpIter", nodename, B2b, parentfunc)) + success = false; + if (! LLTreeRootIter_test<LLTreeIter::DOWN, NODE>("LLTreeDownIter", nodename, B2b, parentfunc)) + success = false; + return success; +} + +template <LLTreeIter::WalkIter DISCRIM, class NODE, typename CHILDITER, + typename CHILDBEGINFUNC, typename CHILDENDFUNC> +bool LLTreeWalkIter_test(const std::string& itername, const std::string& nodename, + CHILDBEGINFUNC childbegin, CHILDENDFUNC childend) +{ + typename LLPtrTo<NODE>::type root(example_tree<NODE>()); + Cleanup<typename LLPtrTo<NODE>::type> cleanup(root); + std::ostringstream desc; + desc << itername << '<' << nodename << "> from " << root->name(); + if (! verify(desc.str(), + boost::make_iterator_range(LLTreeWalkIter<DISCRIM, NODE, CHILDITER>(root, + childbegin, + childend), + LLTreeWalkIter<DISCRIM, NODE, CHILDITER>()), + WalkExpected<DISCRIM>())) + return false; + // Try instantiating an iterator with NULL (that is, a default-constructed + // node pointer). This test is less about "did we iterate once?" than "did + // we avoid blowing up?" + for (LLTreeWalkIter<DISCRIM, NODE, CHILDITER> twi = LLTreeWalkIter<DISCRIM, NODE, CHILDITER>(typename LLPtrTo<NODE>::type(), + childbegin, + childend), + twend; + twi != twend; /* ++twi */) // incrementing is moot, and MSVC complains + { + std::cout << itername << '<' << nodename << ">(NULL)\n"; + return false; + } + return true; +} + +template <class NODE, typename CHILDITER, + typename PARENTFUNC, typename CHILDBEGINFUNC, typename CHILDENDFUNC> +bool LLTreeIter_tests(const std::string& nodename, + PARENTFUNC parentfunc, CHILDBEGINFUNC childbegin, CHILDENDFUNC childend) +{ + bool success = true; + if (! LLTreeUpIter_test<NODE, CHILDITER>(nodename, parentfunc, childbegin)) + success = false; +/*==========================================================================*| + LLTreeIter_test<NODE, LLTreeDFSIter<NODE, CHILDITER> >("LLTreeDFSIter", nodename, + childbegin, childend); + LLTreeIter_test<NODE, LLTreeDFSPostIter<NODE, CHILDITER> >("LLTreeDFSPostIter", nodename, + childbegin, childend); + LLTreeIter_test<NODE, LLTreeBFSIter<NODE, CHILDITER> >("LLTreeBFSIter", nodename, + childbegin, childend); +|*==========================================================================*/ + if (! LLTreeWalkIter_test<LLTreeIter::DFS_PRE, NODE, CHILDITER>("LLTreeDFSIter", nodename, + childbegin, childend)) + success = false; + if (! LLTreeWalkIter_test<LLTreeIter::DFS_POST, NODE, CHILDITER>("LLTreeDFSPostIter", nodename, + childbegin, childend)) + success = false; + if (! LLTreeWalkIter_test<LLTreeIter::BFS, NODE, CHILDITER>("LLTreeBFSIter", nodename, + childbegin, childend)) + success = false; + return success; +} + +namespace tut +{ + template<> template<> + void iter_object::test<3>() + { +// set_test_name("LLTreeIter tests"); + ensure(LLTreeIter_tests<TreeNode, TreeNode::child_iterator> + ("TreeNode", + boost::bind(&TreeNode::getParent, _1), + boost::bind(&TreeNode::child_begin, _1), + boost::bind(&TreeNode::child_end, _1))); + ensure(LLTreeIter_tests<PlainTree, LLLinkedIter<PlainTree> > + ("PlainTree", + boost::bind(&PlainTree::mParent, _1), + PlainTree_child_begin, + PlainTree_child_end)); + } + + template<> template<> + void iter_object::test<4>() + { +// set_test_name("getRootRange() tests"); + // This test function illustrates the looping techniques described in the + // comments for the getRootRange() free function, the + // EnhancedTreeNode::root_range template and the + // EnhancedTreeNode::getRootRange() method. Obviously the BOOST_FOREACH() + // forms are more succinct. + TreeNodePtr tnroot(example_tree<TreeNode>()); + TreeNodePtr tnB2b(get_B2b<TreeNode, TreeNode::child_iterator> + (tnroot, boost::bind(&TreeNode::child_begin, _1))); + + std::string desc1("BOOST_FOREACH(TreeNodePr, getRootRange<LLTreeIter::UP>(tnB2b))"); +// std::cout << desc1 << "\n"; + // Although we've commented out the output statement, ensure that the + // loop construct is still valid, as promised by the getRootRange() + // documentation. + BOOST_FOREACH(TreeNodePtr node, getRootRange<LLTreeIter::UP>(tnB2b)) + { +// std::cout << node->name() << '\n'; + } + ensure(desc1, + verify(desc1, getRootRange<LLTreeIter::UP>(tnB2b), RootExpected<LLTreeIter::UP>())); + + EnhancedTreeNodePtr etnroot(example_tree<EnhancedTreeNode>()); + EnhancedTreeNodePtr etnB2b(get_B2b<EnhancedTreeNode, EnhancedTreeNode::child_iterator> + (etnroot, boost::bind(&EnhancedTreeNode::child_begin, _1))); + +// std::cout << "EnhancedTreeNode::root_range<LLTreeIter::DOWN>::type range =\n" +// << " etnB2b->getRootRange<LLTreeIter::DOWN>();\n" +// << "for (EnhancedTreeNode::root_range<LLTreeIter::DOWN>::type::iterator ri = range.begin();\n" +// << " ri != range.end(); ++ri)\n"; + EnhancedTreeNode::root_range<LLTreeIter::DOWN>::type range = + etnB2b->getRootRange<LLTreeIter::DOWN>(); + for (EnhancedTreeNode::root_range<LLTreeIter::DOWN>::type::iterator ri = range.begin(); + ri != range.end(); ++ri) + { +// std::cout << (*ri)->name() << '\n'; + } + + std::string desc2("BOOST_FOREACH(EnhancedTreeNodePtr node, etnB2b->getRootRange<LLTreeIter::UP>())"); +// std::cout << desc2 << '\n'; + BOOST_FOREACH(EnhancedTreeNodePtr node, etnB2b->getRootRange<LLTreeIter::UP>()) + { +// std::cout << node->name() << '\n'; + } + ensure(desc2, + verify(desc2, etnB2b->getRootRange<LLTreeIter::UP>(), RootExpected<LLTreeIter::UP>())); + } + + template<> template<> + void iter_object::test<5>() + { +// set_test_name("getWalkRange() tests"); + // This test function doesn't illustrate the looping permutations for + // getWalkRange(); see getRootRange_tests() for such examples. This + // function simply verifies that they all work. + + // TreeNode, using helper function + TreeNodePtr tnroot(example_tree<TreeNode>()); + std::string desc_tnpre("getWalkRange<LLTreeIter::DFS_PRE>(tnroot)"); + ensure(desc_tnpre, + verify(desc_tnpre, + getWalkRange<LLTreeIter::DFS_PRE>(tnroot), + WalkExpected<LLTreeIter::DFS_PRE>())); + std::string desc_tnpost("getWalkRange<LLTreeIter::DFS_POST>(tnroot)"); + ensure(desc_tnpost, + verify(desc_tnpost, + getWalkRange<LLTreeIter::DFS_POST>(tnroot), + WalkExpected<LLTreeIter::DFS_POST>())); + std::string desc_tnb("getWalkRange<LLTreeIter::BFS>(tnroot)"); + ensure(desc_tnb, + verify(desc_tnb, + getWalkRange<LLTreeIter::BFS>(tnroot), + WalkExpected<LLTreeIter::BFS>())); + + // EnhancedTreeNode, using method + EnhancedTreeNodePtr etnroot(example_tree<EnhancedTreeNode>()); + std::string desc_etnpre("etnroot->getWalkRange<LLTreeIter::DFS_PRE>()"); + ensure(desc_etnpre, + verify(desc_etnpre, + etnroot->getWalkRange<LLTreeIter::DFS_PRE>(), + WalkExpected<LLTreeIter::DFS_PRE>())); + std::string desc_etnpost("etnroot->getWalkRange<LLTreeIter::DFS_POST>()"); + ensure(desc_etnpost, + verify(desc_etnpost, + etnroot->getWalkRange<LLTreeIter::DFS_POST>(), + WalkExpected<LLTreeIter::DFS_POST>())); + std::string desc_etnb("etnroot->getWalkRange<LLTreeIter::BFS>()"); + ensure(desc_etnb, + verify(desc_etnb, + etnroot->getWalkRange<LLTreeIter::BFS>(), + WalkExpected<LLTreeIter::BFS>())); + + // PlainTree, using helper function + PlainTree* ptroot(example_tree<PlainTree>()); + Cleanup<PlainTree*> cleanup(ptroot); + std::string desc_ptpre("getWalkRange<LLTreeIter::DFS_PRE>(ptroot)"); + ensure(desc_ptpre, + verify(desc_ptpre, + getWalkRange<LLTreeIter::DFS_PRE>(ptroot), + WalkExpected<LLTreeIter::DFS_PRE>())); + std::string desc_ptpost("getWalkRange<LLTreeIter::DFS_POST>(ptroot)"); + ensure(desc_ptpost, + verify(desc_ptpost, + getWalkRange<LLTreeIter::DFS_POST>(ptroot), + WalkExpected<LLTreeIter::DFS_POST>())); + std::string desc_ptb("getWalkRange<LLTreeIter::BFS>(ptroot)"); + ensure(desc_ptb, + verify(desc_ptb, + getWalkRange<LLTreeIter::BFS>(ptroot), + WalkExpected<LLTreeIter::BFS>())); + } +} // tut diff --git a/indra/llcommon/tests/lluri_test.cpp b/indra/llcommon/tests/lluri_test.cpp new file mode 100644 index 0000000000..0a7c37d4b9 --- /dev/null +++ b/indra/llcommon/tests/lluri_test.cpp @@ -0,0 +1,370 @@ +/** + * @file lluri_test.cpp + * @brief LLURI unit tests + * @date September 2006 + * + * $LicenseInfo:firstyear=2006&license=viewergpl$ + * + * Copyright (c) 2006-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "../llsd.h" +#include "../lluri.h" + +#include "../test/lltut.h" + +namespace tut +{ + struct URITestData { + void checkParts(const LLURI& u, + const char* expectedScheme, + const char* expectedOpaque, + const char* expectedAuthority, + const char* expectedPath, + const char* expectedQuery = "") + { + ensure_equals("scheme", u.scheme(), expectedScheme); + ensure_equals("opaque", u.opaque(), expectedOpaque); + ensure_equals("authority", u.authority(), expectedAuthority); + ensure_equals("path", u.path(), expectedPath); + ensure_equals("query", u.query(), expectedQuery); + } + + void escapeRoundTrip(const std::string& uri_raw_1) + { + std::string uri_esc_1(LLURI::escape(uri_raw_1)); + std::string uri_raw_2(LLURI::unescape(uri_esc_1)); + ensure_equals("escape/unescape raw", uri_raw_2, uri_raw_1); + std::string uri_esc_2(LLURI::escape(uri_raw_2)); + ensure_equals("escape/unescape escaped", uri_esc_2, uri_esc_1); + } + }; + + typedef test_group<URITestData> URITestGroup; + typedef URITestGroup::object URITestObject; + + URITestGroup uriTestGroup("LLURI"); + + template<> template<> + void URITestObject::test<1>() + { + LLURI u("http://abc.com/def/ghi?x=37&y=hello"); + + ensure_equals("scheme", u.scheme(), "http"); + ensure_equals("authority", u.authority(), "abc.com"); + ensure_equals("path", u.path(), "/def/ghi"); + ensure_equals("query", u.query(), "x=37&y=hello"); + + ensure_equals("host name", u.hostName(), "abc.com"); + ensure_equals("host port", u.hostPort(), 80); + + LLSD query = u.queryMap(); + ensure_equals("query x", query["x"].asInteger(), 37); + ensure_equals("query y", query["y"].asString(), "hello"); + + query = LLURI::queryMap("x=22.23&y=https://lindenlab.com/"); + ensure_equals("query x", query["x"].asReal(), 22.23); + ensure_equals("query y", query["y"].asURI().asString(), "https://lindenlab.com/"); + } + + template<> template<> + void URITestObject::test<2>() + { + // empty string + checkParts(LLURI(""), "", "", "", ""); + } + + template<> template<> + void URITestObject::test<3>() + { + // no scheme + checkParts(LLURI("foo"), "", "foo", "", ""); + checkParts(LLURI("foo%3A"), "", "foo:", "", ""); + } + + template<> template<> + void URITestObject::test<4>() + { + // scheme w/o paths + checkParts(LLURI("mailto:zero@ll.com"), + "mailto", "zero@ll.com", "", ""); + checkParts(LLURI("silly://abc/def?foo"), + "silly", "//abc/def?foo", "", ""); + } + + template<> template<> + void URITestObject::test<5>() + { + // authority section + checkParts(LLURI("http:///"), + "http", "///", "", "/"); + + checkParts(LLURI("http://abc"), + "http", "//abc", "abc", ""); + + checkParts(LLURI("http://a%2Fb/cd"), + "http", "//a/b/cd", "a/b", "/cd"); + + checkParts(LLURI("http://host?"), + "http", "//host?", "host", ""); + } + + template<> template<> + void URITestObject::test<6>() + { + // path section + checkParts(LLURI("http://host/a/b/"), + "http", "//host/a/b/", "host", "/a/b/"); + + checkParts(LLURI("http://host/a%3Fb/"), + "http", "//host/a?b/", "host", "/a?b/"); + + checkParts(LLURI("http://host/a:b/"), + "http", "//host/a:b/", "host", "/a:b/"); + } + + template<> template<> + void URITestObject::test<7>() + { + // query string + checkParts(LLURI("http://host/?"), + "http", "//host/?", "host", "/", ""); + + checkParts(LLURI("http://host/?x"), + "http", "//host/?x", "host", "/", "x"); + + checkParts(LLURI("http://host/??"), + "http", "//host/??", "host", "/", "?"); + + checkParts(LLURI("http://host/?%3F"), + "http", "//host/??", "host", "/", "?"); + } + + template<> template<> + void URITestObject::test<8>() + { + LLSD path; + path.append("x"); + path.append("123"); + checkParts(LLURI::buildHTTP("host", path), + "http", "//host/x/123", "host", "/x/123"); + + LLSD query; + query["123"] = "12"; + query["abcd"] = "abc"; + checkParts(LLURI::buildHTTP("host", path, query), + "http", "//host/x/123?123=12&abcd=abc", + "host", "/x/123", "123=12&abcd=abc"); + } + + template<> template<> + void URITestObject::test<9>() + { + // test unescaped path components + LLSD path; + path.append("x@*//*$&^"); + path.append("123"); + checkParts(LLURI::buildHTTP("host", path), + "http", "//host/x@*//*$&^/123", "host", "/x@*//*$&^/123"); + } + + template<> template<> + void URITestObject::test<10>() + { + // test unescaped query components + LLSD path; + path.append("x"); + path.append("123"); + LLSD query; + query["123"] = "?&*#//"; + query["**@&?//"] = "abc"; + checkParts(LLURI::buildHTTP("host", path, query), + "http", "//host/x/123?**@&?//=abc&123=?&*#//", + "host", "/x/123", "**@&?//=abc&123=?&*#//"); + } + + template<> template<> + void URITestObject::test<11>() + { + // test unescaped host components + LLSD path; + path.append("x"); + path.append("123"); + LLSD query; + query["123"] = "12"; + query["abcd"] = "abc"; + checkParts(LLURI::buildHTTP("hi123*33--}{:portstuffs", path, query), + "http", "//hi123*33--}{:portstuffs/x/123?123=12&abcd=abc", + "hi123*33--}{:portstuffs", "/x/123", "123=12&abcd=abc"); + } + + template<> template<> + void URITestObject::test<12>() + { + // test funky host_port values that are actually prefixes + + checkParts(LLURI::buildHTTP("http://example.com:8080", LLSD()), + "http", "//example.com:8080", + "example.com:8080", ""); + + checkParts(LLURI::buildHTTP("http://example.com:8080/", LLSD()), + "http", "//example.com:8080/", + "example.com:8080", "/"); + + checkParts(LLURI::buildHTTP("http://example.com:8080/a/b", LLSD()), + "http", "//example.com:8080/a/b", + "example.com:8080", "/a/b"); + } + + template<> template<> + void URITestObject::test<13>() + { + const std::string unreserved = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789" + "-._~"; + // test escape + ensure_equals("escaping", LLURI::escape("abcdefg", "abcdef"), "abcdef%67"); + ensure_equals("escaping", LLURI::escape("|/&\\+-_!@", ""), "%7C%2F%26%5C%2B%2D%5F%21%40"); + ensure_equals("escaping as query variable", + LLURI::escape("http://10.0.1.4:12032/agent/god/agent-id/map/layer/?resume=http://station3.ll.com:12032/agent/203ad6df-b522-491d-ba48-4e24eb57aeff/send-postcard", unreserved + ":@!$'()*+,="), + "http:%2F%2F10.0.1.4:12032%2Fagent%2Fgod%2Fagent-id%2Fmap%2Flayer%2F%3Fresume=http:%2F%2Fstation3.ll.com:12032%2Fagent%2F203ad6df-b522-491d-ba48-4e24eb57aeff%2Fsend-postcard"); + // French cedilla (C with squiggle, like in the word Francais) is UTF-8 C3 A7 + +#if LL_WINDOWS +#pragma warning(disable: 4309) +#endif + + std::string cedilla; + cedilla.push_back( (char)0xC3 ); + cedilla.push_back( (char)0xA7 ); + ensure_equals("escape UTF8", LLURI::escape( cedilla, unreserved), "%C3%A7"); + } + + + template<> template<> + void URITestObject::test<14>() + { + // make sure escape and unescape of empty strings return empty + // strings. + std::string uri_esc(LLURI::escape("")); + ensure("escape string empty", uri_esc.empty()); + std::string uri_raw(LLURI::unescape("")); + ensure("unescape string empty", uri_raw.empty()); + } + + template<> template<> + void URITestObject::test<15>() + { + // do some round-trip tests + escapeRoundTrip("http://secondlife.com"); + escapeRoundTrip("http://secondlife.com/url with spaces"); + escapeRoundTrip("http://bad[domain]name.com/"); + escapeRoundTrip("ftp://bill.gates@ms/micro$oft.com/c:\\autoexec.bat"); + escapeRoundTrip(""); + } + + template<> template<> + void URITestObject::test<16>() + { + // Test the default escaping + // yes -- this mangles the url. This is expected behavior + std::string simple("http://secondlife.com"); + ensure_equals( + "simple http", + LLURI::escape(simple), + "http%3A%2F%2Fsecondlife.com"); + ensure_equals( + "needs escape", + LLURI::escape("http://get.secondlife.com/windows viewer"), + "http%3A%2F%2Fget.secondlife.com%2Fwindows%20viewer"); + } + + template<> template<> + void URITestObject::test<17>() + { + // do some round-trip tests with very long strings. + escapeRoundTrip("Welcome to Second Life.We hope you'll have a richly rewarding experience, filled with creativity, self expression and fun.The goals of the Community Standards are simple: treat each other with respect and without harassment, adhere to local standards as indicated by simulator ratings, and refrain from any hate activity which slurs a real-world individual or real-world community. Behavioral Guidelines - The Big Six"); + escapeRoundTrip( + "'asset_data':b(12100){'task_id':ucc706f2d-0b68-68f8-11a4-f1043ff35ca0}\n{\n\tname\tObject|\n\tpermissions 0\n\t{\n\t\tbase_mask\t7fffffff\n\t\towner_mask\t7fffffff\n\t\tgroup_mask\t00000000\n\t\teveryone_mask\t00000000\n\t\tnext_owner_mask\t7fffffff\n\t\tcreator_id\t13fd9595-a47b-4d64-a5fb-6da645f038e0\n\t\towner_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\tlast_owner_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\tgroup_id\t00000000-0000-0000-0000-000000000000\n\t}\n\tlocal_id\t217444921\n\ttotal_crc\t323\n\ttype\t2\n\ttask_valid\t2\n\ttravel_access\t13\n\tdisplayopts\t2\n\tdisplaytype\tv\n\tpos\t-0.368634403\t0.00781063363\t-0.569040775\n\toldpos\t150.117996\t25.8658009\t8.19664001\n\trotation\t-0.06293071806430816650390625\t-0.6995697021484375\t-0.7002241611480712890625\t0.1277817934751510620117188\n\tchildpos\t-0.00499999989\t-0.0359999985\t0.307999998\n\tchildrot\t-0.515492737293243408203125\t-0.46601200103759765625\t0.529055416584014892578125\t0.4870323240756988525390625\n\tscale" + "\t0.074629\t0.289956\t0.01\n\tsit_offset\t0\t0\t0\n\tcamera_eye_offset\t0\t0\t0\n\tcamera_at_offset\t0\t0\t0\n\tsit_quat\t0\t0\t0\t1\n\tsit_hint\t0\n\tstate\t160\n\tmaterial\t3\n\tsoundid\t00000000-0000-0000-0000-000000000000\n\tsoundgain\t0\n\tsoundradius\t0\n\tsoundflags\t0\n\ttextcolor\t0 0 0 1\n\tselected\t0\n\tselector\t00000000-0000-0000-0000-000000000000\n\tusephysics\t0\n\trotate_x\t1\n\trotate_y\t1\n\trotate_z\t1\n\tphantom\t0\n\tremote_script_access_pin\t0\n\tvolume_detect\t0\n\tblock_grabs\t0\n\tdie_at_edge\t0\n\treturn_at_edge\t0\n\ttemporary\t0\n\tsandbox\t0\n\tsandboxhome\t0\t0\t0\n\tshape 0\n\t{\n\t\tpath 0\n\t\t{\n\t\t\tcurve\t16\n\t\t\tbegin\t0\n\t\t\tend\t1\n\t\t\tscale_x\t1\n\t\t\tscale_y\t1\n\t\t\tshear_x\t0\n\t\t\tshear_y\t0\n\t\t\ttwist\t0\n\t\t\ttwist_begin\t0\n\t\t\tradius_offset\t0\n\t\t\ttaper_x\t0\n\t\t\ttaper_y\t0\n\t\t\trevolutions\t1\n\t\t\tskew\t0\n\t\t}\n\t\tprofile 0\n\t\t{\n\t\t\tcurve\t1\n\t\t\tbegin\t0\n\t\t\tend\t1\n\t\t\thollow\t0\n\t\t}\n\t}\n\tf" + "aces\t6\n\t{\n\t\timageid\tddde1ffc-678b-3cda-1748-513086bdf01b\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tf54a0c32-3cd1-d49a-5b4f-7b792bebc204\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tf54a0c32-3cd1-d49a-5b4f-7b792bebc204\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tf54a0c32-3cd1-d49a-5b4f-7b792bebc204\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tf54a0c32-3cd1-d49a-5b4f-7b792bebc204" + "\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tddde1ffc-678b-3cda-1748-513086bdf01b\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t-1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\tps_next_crc\t1\n\tgpw_bias\t1\n\tip\t0\n\tcomplete\tTRUE\n\tdelay\t50000\n\tnextstart\t0\n\tbirthtime\t1061088050622956\n\treztime\t1094866329019785\n\tparceltime\t1133568981980596\n\ttax_rate\t1.00084\n\tscratchpad\t0\n\t{\n\t\n\t}\n\tsale_info\t0\n\t{\n\t\tsale_type\tnot\n\t\tsale_price\t10\n\t}\n\tcorrect_family_id\t00000000-0000-0000-0000-000000000000\n\thas_rezzed\t0\n\tpre_link_base_mask\t7fffffff\n\tlinked \tchild\n\tdefault_pay_price\t-2\t1\t5\t10\t20\n}\n{'task_id':u61fa7364-e151-0597-774c-523312dae31b}\n{\n\tname\tObject|\n\tpermissions 0\n\t{\n\t\tbase_mask\t7fffff" + "ff\n\t\towner_mask\t7fffffff\n\t\tgroup_mask\t00000000\n\t\teveryone_mask\t00000000\n\t\tnext_owner_mask\t7fffffff\n\t\tcreator_id\t13fd9595-a47b-4d64-a5fb-6da645f038e0\n\t\towner_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\tlast_owner_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\tgroup_id\t00000000-0000-0000-0000-000000000000\n\t}\n\tlocal_id\t217444922\n\ttotal_crc\t324\n\ttype\t2\n\ttask_valid\t2\n\ttravel_access\t13\n\tdisplayopts\t2\n\tdisplaytype\tv\n\tpos\t-0.367110789\t0.00780026987\t-0.566269755\n\toldpos\t150.115005\t25.8479004\t8.18669987\n\trotation\t0.47332942485809326171875\t-0.380102097988128662109375\t-0.5734078884124755859375\t0.550168216228485107421875\n\tchildpos\t-0.00499999989\t-0.0370000005\t0.305000007\n\tchildrot\t-0.736649334430694580078125\t-0.03042060509324073791503906\t-0.02784589119255542755126953\t0.67501628398895263671875\n\tscale\t0.074629\t0.289956\t0.01\n\tsit_offset\t0\t0\t0\n\tcamera_eye_offset\t0\t0\t0\n\tcamera_at_offset\t0\t0\t0\n\tsit_quat\t0\t" + "0\t0\t1\n\tsit_hint\t0\n\tstate\t160\n\tmaterial\t3\n\tsoundid\t00000000-0000-0000-0000-000000000000\n\tsoundgain\t0\n\tsoundradius\t0\n\tsoundflags\t0\n\ttextcolor\t0 0 0 1\n\tselected\t0\n\tselector\t00000000-0000-0000-0000-000000000000\n\tusephysics\t0\n\trotate_x\t1\n\trotate_y\t1\n\trotate_z\t1\n\tphantom\t0\n\tremote_script_access_pin\t0\n\tvolume_detect\t0\n\tblock_grabs\t0\n\tdie_at_edge\t0\n\treturn_at_edge\t0\n\ttemporary\t0\n\tsandbox\t0\n\tsandboxhome\t0\t0\t0\n\tshape 0\n\t{\n\t\tpath 0\n\t\t{\n\t\t\tcurve\t16\n\t\t\tbegin\t0\n\t\t\tend\t1\n\t\t\tscale_x\t1\n\t\t\tscale_y\t1\n\t\t\tshear_x\t0\n\t\t\tshear_y\t0\n\t\t\ttwist\t0\n\t\t\ttwist_begin\t0\n\t\t\tradius_offset\t0\n\t\t\ttaper_x\t0\n\t\t\ttaper_y\t0\n\t\t\trevolutions\t1\n\t\t\tskew\t0\n\t\t}\n\t\tprofile 0\n\t\t{\n\t\t\tcurve\t1\n\t\t\tbegin\t0\n\t\t\tend\t1\n\t\t\thollow\t0\n\t\t}\n\t}\n\tfaces\t6\n\t{\n\t\timageid\tddde1ffc-678b-3cda-1748-513086bdf01b\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t" + "\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tf54a0c32-3cd1-d49a-5b4f-7b792bebc204\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tf54a0c32-3cd1-d49a-5b4f-7b792bebc204\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tf54a0c32-3cd1-d49a-5b4f-7b792bebc204\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tf54a0c32-3cd1-d49a-5b4f-7b792bebc204\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t" + "\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tddde1ffc-678b-3cda-1748-513086bdf01b\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t-1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\tps_next_crc\t1\n\tgpw_bias\t1\n\tip\t0\n\tcomplete\tTRUE\n\tdelay\t50000\n\tnextstart\t0\n\tbirthtime\t1061087839248891\n\treztime\t1094866329020800\n\tparceltime\t1133568981981983\n\ttax_rate\t1.00084\n\tscratchpad\t0\n\t{\n\t\n\t}\n\tsale_info\t0\n\t{\n\t\tsale_type\tnot\n\t\tsale_price\t10\n\t}\n\tcorrect_family_id\t00000000-0000-0000-0000-000000000000\n\thas_rezzed\t0\n\tpre_link_base_mask\t7fffffff\n\tlinked \tchild\n\tdefault_pay_price\t-2\t1\t5\t10\t20\n}\n{'task_id':ub8d68643-7dd8-57af-0d24-8790032aed0c}\n{\n\tname\tObject|\n\tpermissions 0\n\t{\n\t\tbase_mask\t7fffffff\n\t\towner_mask\t7fffffff\n\t\tgroup_mask\t00000000\n\t\teveryone_mask\t00000000\n\t\tnext_owner_mask\t7fffffff\n\t\tcreat" + "or_id\t13fd9595-a47b-4d64-a5fb-6da645f038e0\n\t\towner_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\tlast_owner_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\tgroup_id\t00000000-0000-0000-0000-000000000000\n\t}\n\tlocal_id\t217444923\n\ttotal_crc\t235\n\ttype\t2\n\ttask_valid\t2\n\ttravel_access\t13\n\tdisplayopts\t2\n\tdisplaytype\tv\n\tpos\t-0.120029509\t-0.00284469454\t-0.0302077383\n\toldpos\t150.710999\t25.8584995\t8.19172001\n\trotation\t0.145459949970245361328125\t-0.1646589934825897216796875\t0.659558117389678955078125\t-0.718826770782470703125\n\tchildpos\t0\t-0.182999998\t-0.26699999\n\tchildrot\t0.991444766521453857421875\t3.271923924330621957778931e-05\t-0.0002416197530692443251609802\t0.1305266767740249633789062\n\tscale\t0.0382982\t0.205957\t0.368276\n\tsit_offset\t0\t0\t0\n\tcamera_eye_offset\t0\t0\t0\n\tcamera_at_offset\t0\t0\t0\n\tsit_quat\t0\t0\t0\t1\n\tsit_hint\t0\n\tstate\t160\n\tmaterial\t3\n\tsoundid\t00000000-0000-0000-0000-000000000000\n\tsoundgain\t0\n\tsoundra" + "dius\t0\n\tsoundflags\t0\n\ttextcolor\t0 0 0 1\n\tselected\t0\n\tselector\t00000000-0000-0000-0000-000000000000\n\tusephysics\t0\n\trotate_x\t1\n\trotate_y\t1\n\trotate_z\t1\n\tphantom\t0\n\tremote_script_access_pin\t0\n\tvolume_detect\t0\n\tblock_grabs\t0\n\tdie_at_edge\t0\n\treturn_at_edge\t0\n\ttemporary\t0\n\tsandbox\t0\n\tsandboxhome\t0\t0\t0\n\tshape 0\n\t{\n\t\tpath 0\n\t\t{\n\t\t\tcurve\t32\n\t\t\tbegin\t0.3\n\t\t\tend\t0.65\n\t\t\tscale_x\t1\n\t\t\tscale_y\t0.05\n\t\t\tshear_x\t0\n\t\t\tshear_y\t0\n\t\t\ttwist\t0\n\t\t\ttwist_begin\t0\n\t\t\tradius_offset\t0\n\t\t\ttaper_x\t0\n\t\t\ttaper_y\t0\n\t\t\trevolutions\t1\n\t\t\tskew\t0\n\t\t}\n\t\tprofile 0\n\t\t{\n\t\t\tcurve\t0\n\t\t\tbegin\t0\n\t\t\tend\t1\n\t\t\thollow\t0\n\t\t}\n\t}\n\tfaces\t3\n\t{\n\t\timageid\te7150bed-3e3e-c698-eb15-d17b178148af\n\t\tcolors\t0.843137 0.156863 0.156863 1\n\t\tscales\t15\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t-1.57084\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0" + "\n\t}\n\t{\n\t\timageid\te7150bed-3e3e-c698-eb15-d17b178148af\n\t\tcolors\t0.843137 0.156863 0.156863 1\n\t\tscales\t15\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t-1.57084\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\te7150bed-3e3e-c698-eb15-d17b178148af\n\t\tcolors\t0.843137 0.156863 0.156863 1\n\t\tscales\t15\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t-1.57084\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\tps_next_crc\t1\n\tgpw_bias\t1\n\tip\t0\n\tcomplete\tTRUE\n\tdelay\t50000\n\tnextstart\t0\n\tbirthtime\t1061087534454174\n\treztime\t1094866329021741\n\tparceltime\t1133568981982889\n\ttax_rate\t1.00326\n\tscratchpad\t0\n\t{\n\t\n\t}\n\tsale_info\t0\n\t{\n\t\tsale_type\tnot\n\t\tsale_price\t10\n\t}\n\tcorrect_family_id\t00000000-0000-0000-0000-000000000000\n\thas_rezzed\t0\n\tpre_link_base_mask\t7fffffff\n\tlinked \tchild\n\tdefault_pay_price\t-2\t1\t5\t10\t20\n}\n{'task_id':ue4b19200-9d33-962f-c8c5-6f" + "25be3a3fd0}\n{\n\tname\tApotheosis_Immolaine_tail|\n\tpermissions 0\n\t{\n\t\tbase_mask\t7fffffff\n\t\towner_mask\t7fffffff\n\t\tgroup_mask\t00000000\n\t\teveryone_mask\t00000000\n\t\tnext_owner_mask\t7fffffff\n\t\tcreator_id\t13fd9595-a47b-4d64-a5fb-6da645f038e0\n\t\towner_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\tlast_owner_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\tgroup_id\t00000000-0000-0000-0000-000000000000\n\t}\n\tlocal_id\t217444924\n\ttotal_crc\t675\n\ttype\t1\n\ttask_valid\t2\n\ttravel_access\t13\n\tdisplayopts\t2\n\tdisplaytype\tv\n\tpos\t-0.34780401\t-0.00968400016\t-0.260098994\n\toldpos\t0\t0\t0\n\trotation\t0.73164522647857666015625\t-0.67541944980621337890625\t-0.07733880728483200073242188\t0.05022468417882919311523438\n\tvelocity\t0\t0\t0\n\tangvel\t0\t0\t0\n\tscale\t0.0382982\t0.32228\t0.383834\n\tsit_offset\t0\t0\t0\n\tcamera_eye_offset\t0\t0\t0\n\tcamera_at_offset\t0\t0\t0\n\tsit_quat\t0\t0\t0\t1\n\tsit_hint\t0\n\tstate\t160\n\tmaterial\t3\n\tsoundid\t00000" + "000-0000-0000-0000-000000000000\n\tsoundgain\t0\n\tsoundradius\t0\n\tsoundflags\t0\n\ttextcolor\t0 0 0 1\n\tselected\t0\n\tselector\t00000000-0000-0000-0000-000000000000\n\tusephysics\t0\n\trotate_x\t1\n\trotate_y\t1\n\trotate_z\t1\n\tphantom\t0\n\tremote_script_access_pin\t0\n\tvolume_detect\t0\n\tblock_grabs\t0\n\tdie_at_edge\t0\n\treturn_at_edge\t0\n\ttemporary\t0\n\tsandbox\t0\n\tsandboxhome\t0\t0\t0\n\tshape 0\n\t{\n\t\tpath 0\n\t\t{\n\t\t\tcurve\t32\n\t\t\tbegin\t0.3\n\t\t\tend\t0.65\n\t\t\tscale_x\t1\n\t\t\tscale_y\t0.05\n\t\t\tshear_x\t0\n\t\t\tshear_y\t0\n\t\t\ttwist\t0\n\t\t\ttwist_begin\t0\n\t\t\tradius_offset\t0\n\t\t\ttaper_x\t0\n\t\t\ttaper_y\t0\n\t\t\trevolutions\t1\n\t\t\tskew\t0\n\t\t}\n\t\tprofile 0\n\t\t{\n\t\t\tcurve\t0\n\t\t\tbegin\t0\n\t\t\tend\t1\n\t\t\thollow\t0\n\t\t}\n\t}\n\tfaces\t3\n\t{\n\t\timageid\te7150bed-3e3e-c698-eb15-d17b178148af\n\t\tcolors\t0.843137 0.156863 0.156863 1\n\t\tscales\t15\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t-1" + ".57084\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\te7150bed-3e3e-c698-eb15-d17b178148af\n\t\tcolors\t0.843137 0.156863 0.156863 1\n\t\tscales\t15\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t-1.57084\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\te7150bed-3e3e-c698-eb15-d17b178148af\n\t\tcolors\t0.843137 0.156863 0.156863 1\n\t\tscales\t15\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t-1.57084\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\tps_next_crc\t1\n\tgpw_bias\t1\n\tip\t0\n\tcomplete\tTRUE\n\tdelay\t50000\n\tnextstart\t0\n\tbirthtime\t1061087463950186\n\treztime\t1094866329022555\n\tparceltime\t1133568981984359\n\tdescription\t(No Description)|\n\ttax_rate\t1.01736\n\tnamevalue\tAttachPt U32 RW S 10\n\tnamevalue\tAttachmentOrientation VEC3 RW DS -3.110088, -0.182018, 1.493795\n\tnamevalue\tAttachmentOffset VEC3 RW DS -0.347804, -0.009684, -0.260099\n\tnamevalue\tAttachItemI" + "D STRING RW SV 20f36c3a-b44b-9bc7-87f3-018bfdfc8cda\n\tscratchpad\t0\n\t{\n\t\n\t}\n\tsale_info\t0\n\t{\n\t\tsale_type\tnot\n\t\tsale_price\t10\n\t}\n\torig_asset_id\t8747acbc-d391-1e59-69f1-41d06830e6c0\n\torig_item_id\t20f36c3a-b44b-9bc7-87f3-018bfdfc8cda\n\tfrom_task_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\tcorrect_family_id\t00000000-0000-0000-0000-000000000000\n\thas_rezzed\t0\n\tpre_link_base_mask\t7fffffff\n\tlinked \tlinked\n\tdefault_pay_price\t-2\t1\t5\t10\t20\n}\n"); + } + + + template<> template<> + void URITestObject::test<18>() + { + LLURI u("secondlife:///app/login?first_name=Testert4&last_name=Tester&web_login_key=test"); + // if secondlife is the scheme, LLURI should parse /app/login as path, with no authority + ensure_equals("scheme", u.scheme(), "secondlife"); + ensure_equals("authority", u.authority(), ""); + ensure_equals("path", u.path(), "/app/login"); + ensure_equals("pathmap", u.pathArray()[0].asString(), "app"); + ensure_equals("pathmap", u.pathArray()[1].asString(), "login"); + ensure_equals("query", u.query(), "first_name=Testert4&last_name=Tester&web_login_key=test"); + ensure_equals("query map element", u.queryMap()["last_name"].asString(), "Tester"); + + u = LLURI("secondlife://Da Boom/128/128/128"); + // if secondlife is the scheme, LLURI should parse /128/128/128 as path, with Da Boom as authority + ensure_equals("scheme", u.scheme(), "secondlife"); + ensure_equals("authority", u.authority(), "Da Boom"); + ensure_equals("path", u.path(), "/128/128/128"); + ensure_equals("pathmap", u.pathArray()[0].asString(), "128"); + ensure_equals("pathmap", u.pathArray()[1].asString(), "128"); + ensure_equals("pathmap", u.pathArray()[2].asString(), "128"); + ensure_equals("query", u.query(), ""); + } + + template<> template<> + void URITestObject::test<19>() + { + // Parse about: schemes + LLURI u("about:blank?redirect-http-hack=secondlife%3A%2F%2F%2Fapp%2Flogin%3Ffirst_name%3DCallum%26last_name%3DLinden%26location%3Dspecify%26grid%3Dvaak%26region%3D%2FMorris%2F128%2F128%26web_login_key%3Defaa4795-c2aa-4c58-8966-763c27931e78"); + ensure_equals("scheme", u.scheme(), "about"); + ensure_equals("authority", u.authority(), ""); + ensure_equals("path", u.path(), "blank"); + ensure_equals("pathmap", u.pathArray()[0].asString(), "blank"); + ensure_equals("query", u.query(), "redirect-http-hack=secondlife:///app/login?first_name=Callum&last_name=Linden&location=specify&grid=vaak®ion=/Morris/128/128&web_login_key=efaa4795-c2aa-4c58-8966-763c27931e78"); + ensure_equals("query map element", u.queryMap()["redirect-http-hack"].asString(), "secondlife:///app/login?first_name=Callum&last_name=Linden&location=specify&grid=vaak®ion=/Morris/128/128&web_login_key=efaa4795-c2aa-4c58-8966-763c27931e78"); + } +} + + diff --git a/indra/llcommon/tests/reflection_test.cpp b/indra/llcommon/tests/reflection_test.cpp new file mode 100644 index 0000000000..5263e7fa64 --- /dev/null +++ b/indra/llcommon/tests/reflection_test.cpp @@ -0,0 +1,226 @@ +/** + * @file reflection_test.cpp + * @date May 2006 + * @brief Reflection unit tests. + * + * $LicenseInfo:firstyear=2006&license=viewergpl$ + * + * Copyright (c) 2006-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "../linden_common.h" +#include "../reflective.h" +#include "../metaclasst.h" +#include "../metapropertyt.h" +#include "../stdtypes.h" + +#include "../test/lltut.h" + +namespace tut +{ + class TestAggregatedData : public LLReflective + { + public: + TestAggregatedData() {;} + virtual const LLMetaClass& getMetaClass() const; + + private: + }; + + class TestReflectionData : public LLReflective + { + public: + TestReflectionData() : mInt(42), mString("foo"), mNullPtr(NULL), mPtr(new TestAggregatedData()), mRef(*(new TestAggregatedData)) {;} + virtual ~TestReflectionData() {delete mPtr;} + virtual const LLMetaClass& getMetaClass() const; + + static U32 getPropertyCount() {return 5;} + + private: + + friend class LLMetaClassT<TestReflectionData>; + S32 mInt; + std::string mString; + TestAggregatedData* mNullPtr; + TestAggregatedData* mPtr; + TestAggregatedData mObj; + TestAggregatedData& mRef; + }; +} + +template <> +void LLMetaClassT<tut::TestReflectionData>::reflectProperties(LLMetaClass& meta_class) +{ + reflectProperty(meta_class, "mInt", &tut::TestReflectionData::mInt); + reflectProperty(meta_class, "mString", &tut::TestReflectionData::mString); + reflectPtrProperty(meta_class, "mNullPtr", &tut::TestReflectionData::mNullPtr); + reflectPtrProperty(meta_class, "mPtr", &tut::TestReflectionData::mPtr); + reflectProperty(meta_class, "mObj", &tut::TestReflectionData::mObj); + //reflectProperty(meta_class, "mRef", &tut::TestReflectionData::mRef); // AARGH! +} + +namespace tut +{ + // virtual + const LLMetaClass& TestReflectionData::getMetaClass() const + { + return LLMetaClassT<TestReflectionData>::instance(); + } + + const LLMetaClass& TestAggregatedData::getMetaClass() const + { + return LLMetaClassT<TestAggregatedData>::instance(); + } +} + +namespace tut +{ + typedef tut::test_group<TestReflectionData> TestReflectionGroup; + typedef TestReflectionGroup::object TestReflectionObject; + TestReflectionGroup gTestReflectionGroup("reflection"); + + template<> template<> + void TestReflectionObject::test<1>() + { + // Check properties can be found. + const LLMetaClass& meta_class = LLMetaClassT<TestReflectionData>::instance(); + const LLMetaProperty* null = NULL; + ensure_not_equals(meta_class.findProperty("mInt"), null); + ensure_not_equals(meta_class.findProperty("mString"), null); + } + + template<> template<> + void TestReflectionObject::test<2>() + { + // Check non-existent property cannot be found. + const LLMetaClass& meta_class = LLMetaClassT<TestReflectionData>::instance(); + const LLMetaProperty* null = NULL; + ensure_equals(meta_class.findProperty("foo"), null); + } + + template<> template<> + void TestReflectionObject::test<3>() + { + // Check integer property has correct value. + const LLMetaClass& meta_class = LLMetaClassT<TestReflectionData>::instance(); + ensure_equals(meta_class.findProperty("mInt")->getLLSD(this).asInteger(), 42); + } + + template<> template<> + void TestReflectionObject::test<4>() + { + // Check string property has correct value. + const LLMetaClass& meta_class = LLMetaClassT<TestReflectionData>::instance(); + ensure_equals(meta_class.findProperty("mString")->getLLSD(this).asString(), std::string("foo")); + } + + template<> template<> + void TestReflectionObject::test<5>() + { + // Check NULL reference property has correct value. + const LLMetaClass& meta_class = LLMetaClassT<TestReflectionData>::instance(); + const LLReflective* null = NULL; + ensure_equals(meta_class.findProperty("mNullPtr")->get(this), null); + } + + template<> template<> + void TestReflectionObject::test<6>() + { + // Check reference property has correct value. + const LLMetaClass& meta_class = LLMetaClassT<TestReflectionData>::instance(); + const LLReflective* null = NULL; + const LLReflective* ref = meta_class.findProperty("mPtr")->get(this); + ensure_not_equals(ref, null); + } + + template<> template<> + void TestReflectionObject::test<7>() + { + // Check reflective property has correct value. + const LLMetaClass& meta_class = LLMetaClassT<TestReflectionData>::instance(); + const LLReflective* null = NULL; + const LLReflective* ref = meta_class.findProperty("mObj")->get(this); + ensure_not_equals(ref, null); + } + + template<> template<> + void TestReflectionObject::test<8>() + { + // Check property count. + const LLMetaClass& meta_class = LLMetaClassT<TestReflectionData>::instance(); + ensure_equals(meta_class.getPropertyCount(), TestReflectionData::getPropertyCount()); + } + + template<> template<> + void TestReflectionObject::test<9>() + { + // Check property iteration. + const LLMetaClass& meta_class = LLMetaClassT<TestReflectionData>::instance(); + U32 count = 0; + LLMetaClass::PropertyIterator iter; + for(iter = meta_class.beginProperties(); iter != meta_class.endProperties(); ++iter) + { + ++count; + } + ensure_equals(count, TestReflectionData::getPropertyCount()); + } + + template<> template<> + void TestReflectionObject::test<10>() + { + // Check meta classes of different types do not compare equal. + const LLMetaClass* reflection_data_meta_class = &(LLMetaClassT<TestReflectionData>::instance()); + const LLMetaClass* aggregated_data_meta_class = &(LLMetaClassT<TestAggregatedData>::instance()); + ensure_not_equals(reflection_data_meta_class, aggregated_data_meta_class); + } + + template<> template<> + void TestReflectionObject::test<11>() + { + // Check class cast checks. + const LLMetaClass& meta_class = LLMetaClassT<TestReflectionData>::instance(); + TestAggregatedData* aggregated_data = new TestAggregatedData(); + LLMetaClass::PropertyIterator iter; + U32 exception_count = 0; + for(iter = meta_class.beginProperties(); iter != meta_class.endProperties(); ++iter) + { + try + { + const LLMetaProperty* property = (*iter).second; + const LLReflective* reflective = property->get(aggregated_data); // Wrong reflective type, should throw exception. + + // useless op to get rid of compiler warning. + reflective = NULL; + } + catch(...) + { + ++exception_count; + } + } + ensure_equals(exception_count, getPropertyCount()); + + } +} diff --git a/indra/llcommon/tests/stringize_test.cpp b/indra/llcommon/tests/stringize_test.cpp new file mode 100644 index 0000000000..dd69787a1c --- /dev/null +++ b/indra/llcommon/tests/stringize_test.cpp @@ -0,0 +1,110 @@ +/** + * @file stringize_test.cpp + * @author Nat Goodspeed + * @date 2008-09-12 + * @brief Test of stringize.h + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +/*==========================================================================*| +#if LL_WINDOWS +#pragma warning (disable : 4675) // "resolved by ADL" -- just as I want! +#endif +|*==========================================================================*/ + +// STL headers +#include <iomanip> + +// Precompiled header +#include "linden_common.h" + +// associated header +#include "../stringize.h" + +// std headers +// external library headers +// other Linden headers +#include "../llsd.h" + +#include "../test/lltut.h" + +namespace tut +{ + struct stringize_data + { + stringize_data(): + c('c'), + s(17), + i(34), + l(68), + f(3.14159265358979f), + d(3.14159265358979), + // Including a space differentiates this from + // boost::lexical_cast<std::string>, which doesn't handle embedded + // spaces so well. + abc("abc def") + { + llsd["i"] = i; + llsd["d"] = d; + llsd["abc"] = abc; + } + + char c; + short s; + int i; + long l; + float f; + double d; + std::string abc; + LLSD llsd; + }; + typedef test_group<stringize_data> stringize_group; + typedef stringize_group::object stringize_object; + tut::stringize_group strzgrp("stringize"); + + template<> template<> + void stringize_object::test<1>() + { + ensure_equals(stringize(c), "c"); + ensure_equals(stringize(s), "17"); + ensure_equals(stringize(i), "34"); + ensure_equals(stringize(l), "68"); + ensure_equals(stringize(f), "3.14159"); + ensure_equals(stringize(d), "3.14159"); + ensure_equals(stringize(abc), "abc def"); + ensure_equals(stringize(llsd), "{'abc':'abc def','d':r3.14159,'i':i34}"); + } + + template<> template<> + void stringize_object::test<2>() + { + ensure_equals(STRINGIZE("c is " << c), "c is c"); + ensure_equals(STRINGIZE(std::setprecision(4) << d), "3.142"); + } +} // namespace tut diff --git a/indra/llcommon/tests/wrapllerrs.h b/indra/llcommon/tests/wrapllerrs.h new file mode 100644 index 0000000000..1001ebc466 --- /dev/null +++ b/indra/llcommon/tests/wrapllerrs.h @@ -0,0 +1,56 @@ +/** + * @file wrapllerrs.h + * @author Nat Goodspeed + * @date 2009-03-11 + * @brief Define a class useful for unit tests that engage llerrs (LL_ERRS) functionality + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_WRAPLLERRS_H) +#define LL_WRAPLLERRS_H + +#include "llerrorcontrol.h" + +struct WrapLL_ERRS +{ + WrapLL_ERRS(): + // Resetting Settings discards the default Recorder that writes to + // stderr. Otherwise, expected llerrs (LL_ERRS) messages clutter the + // console output of successful tests, potentially confusing things. + mPriorErrorSettings(LLError::saveAndResetSettings()), + // Save shutdown function called by LL_ERRS + mPriorFatal(LLError::getFatalFunction()) + { + // Make LL_ERRS call our own operator() method + LLError::setFatalFunction(boost::bind(&WrapLL_ERRS::operator(), this, _1)); + } + + ~WrapLL_ERRS() + { + LLError::setFatalFunction(mPriorFatal); + LLError::restoreSettings(mPriorErrorSettings); + } + + struct FatalException: public std::runtime_error + { + FatalException(const std::string& what): std::runtime_error(what) {} + }; + + void operator()(const std::string& message) + { + // Save message for later in case consumer wants to sense the result directly + error = message; + // Also throw an appropriate exception since calling code is likely to + // assume that control won't continue beyond LL_ERRS. + throw FatalException(message); + } + + std::string error; + LLError::Settings* mPriorErrorSettings; + LLError::FatalFunction mPriorFatal; +}; + +#endif /* ! defined(LL_WRAPLLERRS_H) */ |