diff options
| -rwxr-xr-x | indra/llcommon/llerror.cpp | 54 | ||||
| -rwxr-xr-x | indra/llcommon/llerrorcontrol.h | 15 | ||||
| -rwxr-xr-x | indra/llcommon/tests/llerror_test.cpp | 76 | ||||
| -rwxr-xr-x | indra/llcommon/tests/wrapllerrs.h | 113 | ||||
| -rwxr-xr-x | indra/newview/llviewerwindow.cpp | 25 | ||||
| -rwxr-xr-x | indra/test/test.cpp | 62 | 
6 files changed, 205 insertions, 140 deletions
| diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp index 4f721fabdf..09a2d38e1a 100755 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -357,7 +357,7 @@ namespace  	typedef std::map<std::string, LLError::ELevel> LevelMap; -	typedef std::vector<LLError::Recorder*> Recorders; +	typedef std::vector<LLError::RecorderPtr> Recorders;  	typedef std::vector<LLError::CallSite*> CallSiteVector;  	class Globals : public LLSingleton<Globals> @@ -419,8 +419,8 @@ namespace LLError  		LLError::TimeFunction               mTimeFunction;  		Recorders                           mRecorders; -		Recorder*                           mFileRecorder; -		Recorder*                           mFixedBufferRecorder; +		RecorderPtr                           mFileRecorder; +		RecorderPtr                           mFixedBufferRecorder;  		std::string                         mFileRecorderFileName;  		int									mShouldLogCallCounter; @@ -437,14 +437,13 @@ namespace LLError  			mDefaultLevel(LLError::LEVEL_DEBUG),  			mCrashFunction(),  			mTimeFunction(NULL), -			mFileRecorder(NULL), -			mFixedBufferRecorder(NULL), +			mFileRecorder(), +			mFixedBufferRecorder(),  			mShouldLogCallCounter(0)  			{ }  		~Settings()  		{ -			for_each(mRecorders.begin(), mRecorders.end(), DeletePointer());  			mRecorders.clear();  		} @@ -603,11 +602,13 @@ namespace  		// log_to_stderr is only false in the unit and integration tests to keep builds quieter  		if (log_to_stderr && shouldLogToStderr())  		{ -			LLError::addRecorder(new RecordToStderr(stderrLogWantsTime())); +			LLError::RecorderPtr recordToStdErr(new RecordToStderr(stderrLogWantsTime())); +			LLError::addRecorder(recordToStdErr);  		}  #if LL_WINDOWS -		LLError::addRecorder(new RecordToWinDebug); +		LLError::RecorderPtr recordToWinDebug(new RecordToWinDebug()); +		LLError::addRecorder(recordToWinDebug);  #endif  		LogControlFile& e = LogControlFile::fromDirectory(dir); @@ -635,7 +636,8 @@ namespace LLError  		}  		commonInit(dir);  #if !LL_WINDOWS -		addRecorder(new RecordToSyslog(identity)); +		LLError::RecorderPtr recordToSyslog(new RecordToSyslog(identity)); +		addRecorder(recordToSyslog);  #endif  	} @@ -821,9 +823,9 @@ namespace LLError  		return mWantsFunctionName;  	} -	void addRecorder(Recorder* recorder) +	void addRecorder(RecorderPtr recorder)  	{ -		if (recorder == NULL) +		if (!recorder)  		{  			return;  		} @@ -831,9 +833,9 @@ namespace LLError  		s.mRecorders.push_back(recorder);  	} -	void removeRecorder(Recorder* recorder) +	void removeRecorder(RecorderPtr recorder)  	{ -		if (recorder == NULL) +		if (!recorder)  		{  			return;  		} @@ -850,8 +852,7 @@ namespace LLError  		LLError::Settings& s = LLError::Settings::get();  		removeRecorder(s.mFileRecorder); -		delete s.mFileRecorder; -		s.mFileRecorder = NULL; +		s.mFileRecorder.reset();  		s.mFileRecorderFileName.clear();  		if (file_name.empty()) @@ -859,16 +860,13 @@ namespace LLError  			return;  		} -		RecordToFile* f = new RecordToFile(file_name); -		if (!f->okay()) +		RecorderPtr recordToFile(new RecordToFile(file_name)); +		if (boost::dynamic_pointer_cast<RecordToFile>(recordToFile)->okay())  		{ -			delete f; -			return; +			s.mFileRecorderFileName = file_name; +			s.mFileRecorder = recordToFile; +			addRecorder(recordToFile);  		} - -		s.mFileRecorderFileName = file_name; -		s.mFileRecorder = f; -		addRecorder(f);  	}  	void logToFixedBuffer(LLLineBuffer* fixedBuffer) @@ -876,16 +874,16 @@ namespace LLError  		LLError::Settings& s = LLError::Settings::get();  		removeRecorder(s.mFixedBufferRecorder); -		delete s.mFixedBufferRecorder; -		s.mFixedBufferRecorder = NULL; +		s.mFixedBufferRecorder.reset();  		if (!fixedBuffer)  		{  			return;  		} -		s.mFixedBufferRecorder = new RecordToFixedBuffer(fixedBuffer); -		addRecorder(s.mFixedBufferRecorder); +		RecorderPtr recordToFixedBuffer(new RecordToFixedBuffer(fixedBuffer)); +		s.mFixedBufferRecorder = recordToFixedBuffer; +		addRecorder(recordToFixedBuffer);  	}  	std::string logFileName() @@ -906,7 +904,7 @@ namespace  			i != s.mRecorders.end();  			++i)  		{ -			LLError::Recorder* r = *i; +			LLError::RecorderPtr r = *i;  			std::ostringstream message_stream; diff --git a/indra/llcommon/llerrorcontrol.h b/indra/llcommon/llerrorcontrol.h index aab695094c..681f444234 100755 --- a/indra/llcommon/llerrorcontrol.h +++ b/indra/llcommon/llerrorcontrol.h @@ -30,6 +30,7 @@  #include "llerror.h"  #include "boost/function.hpp" +#include "boost/shared_ptr.hpp"  #include <string>  class LLSD; @@ -156,16 +157,14 @@ namespace LLError  				mWantsFunctionName;  	}; +	typedef boost::shared_ptr<Recorder> RecorderPtr; +  	/** -	 * @NOTE: addRecorder() conveys ownership to the underlying Settings -	 * object -- when destroyed, it will @em delete the passed Recorder*! -	 */ -	LL_COMMON_API void addRecorder(Recorder*); -	/** -	 * @NOTE: removeRecorder() reclaims ownership of the Recorder*: its -	 * lifespan becomes the caller's problem. +	 * @NOTE: addRecorder() and removeRecorder() uses the boost::shared_ptr to allow for shared ownership +	 * while still ensuring that the allocated memory is eventually freed  	 */ -	LL_COMMON_API void removeRecorder(Recorder*); +	LL_COMMON_API void addRecorder(RecorderPtr); +	LL_COMMON_API void removeRecorder(RecorderPtr);  		// each error message is passed to each recorder via recordMessage()  	LL_COMMON_API void logToFile(const std::string& filename); diff --git a/indra/llcommon/tests/llerror_test.cpp b/indra/llcommon/tests/llerror_test.cpp index b28c5ba4b3..6cecd6bbc8 100755 --- a/indra/llcommon/tests/llerror_test.cpp +++ b/indra/llcommon/tests/llerror_test.cpp @@ -56,9 +56,9 @@ namespace tut  	{  	public:  		TestRecorder() { mWantsTime = false; } -		~TestRecorder() { LLError::removeRecorder(this); } +		virtual ~TestRecorder() {  } -		void recordMessage(LLError::ELevel level, +		virtual void recordMessage(LLError::ELevel level,  						   const std::string& message)  		{  			mMessages.push_back(message); @@ -85,15 +85,11 @@ namespace tut  	struct ErrorTestData  	{ -		// addRecorder() expects to be able to later delete the passed -		// Recorder*. Even though removeRecorder() reclaims ownership, passing -		// a pointer to a data member rather than a heap Recorder subclass -		// instance would just be Wrong. -		TestRecorder* mRecorder; +		LLError::RecorderPtr mRecorder;  		LLError::Settings* mPriorErrorSettings;  		ErrorTestData(): -			mRecorder(new TestRecorder) +			mRecorder(new TestRecorder())  		{  			fatalWasCalled = false; @@ -106,13 +102,32 @@ namespace tut  		~ErrorTestData()  		{  			LLError::removeRecorder(mRecorder); -			delete mRecorder;  			LLError::restoreSettings(mPriorErrorSettings);  		} +		int countMessages() +		{ +			return boost::dynamic_pointer_cast<TestRecorder>(mRecorder)->countMessages(); +		} + +		void clearMessages() +		{ +			boost::dynamic_pointer_cast<TestRecorder>(mRecorder)->clearMessages(); +		} + +		void setWantsTime(bool t) +		{ +			boost::dynamic_pointer_cast<TestRecorder>(mRecorder)->setWantsTime(t); +		} + +		std::string message(int n) +		{ +			return boost::dynamic_pointer_cast<TestRecorder>(mRecorder)->message(n); +		} +  		void ensure_message_count(int expectedCount)  		{ -			ensure_equals("message count", mRecorder->countMessages(), expectedCount); +			ensure_equals("message count", countMessages(), expectedCount);  		}  		void ensure_message_contains(int n, const std::string& expectedText) @@ -120,7 +135,7 @@ namespace tut  			std::ostringstream test_name;  			test_name << "testing message " << n; -			ensure_contains(test_name.str(), mRecorder->message(n), expectedText); +			ensure_contains(test_name.str(), message(n), expectedText);  		}  		void ensure_message_does_not_contain(int n, const std::string& expectedText) @@ -128,7 +143,7 @@ namespace tut  			std::ostringstream test_name;  			test_name << "testing message " << n; -			ensure_does_not_contain(test_name.str(), mRecorder->message(n), expectedText); +			ensure_does_not_contain(test_name.str(), message(n), expectedText);  		}  	}; @@ -385,15 +400,15 @@ namespace  	}  	typedef std::string (*LogFromFunction)(bool); -	void testLogName(tut::TestRecorder* recorder, LogFromFunction f, +	void testLogName(LLError::RecorderPtr recorder, LogFromFunction f,  		const std::string& class_name = "")  	{ -		recorder->clearMessages(); +		boost::dynamic_pointer_cast<tut::TestRecorder>(recorder)->clearMessages();  		std::string name = f(false);  		f(true); -		std::string messageWithoutName = recorder->message(0); -		std::string messageWithName = recorder->message(1); +		std::string messageWithoutName = boost::dynamic_pointer_cast<tut::TestRecorder>(recorder)->message(0); +		std::string messageWithName = boost::dynamic_pointer_cast<tut::TestRecorder>(recorder)->message(1);  		ensure_has(name + " logged without name",  			messageWithoutName, name); @@ -528,12 +543,12 @@ namespace tut  	{  		LLError::setTimeFunction(roswell); -		mRecorder->setWantsTime(false); +		setWantsTime(false);  		ufoSighting();  		ensure_message_contains(0, "ufo");  		ensure_message_does_not_contain(0, roswell()); -		mRecorder->setWantsTime(true); +		setWantsTime(true);  		ufoSighting();  		ensure_message_contains(1, "ufo");  		ensure_message_contains(1, roswell()); @@ -545,13 +560,13 @@ namespace tut  	{  		LLError::setPrintLocation(true);  		LLError::setTimeFunction(roswell); -		mRecorder->setWantsTime(true); +		setWantsTime(true);  		std::string location,  					function;  		writeReturningLocationAndFunction(location, function);  		ensure_equals("order is location time type function message", -			mRecorder->message(0), +			message(0),  			location + roswell() + " INFO: " + function + ": apple");  	} @@ -559,19 +574,19 @@ namespace tut  		// multiple recorders  	void ErrorTestObject::test<11>()  	{ -		TestRecorder* altRecorder(new TestRecorder); +		LLError::RecorderPtr altRecorder(new TestRecorder());  		LLError::addRecorder(altRecorder);  		LL_INFOS() << "boo" << LL_ENDL;  		ensure_message_contains(0, "boo"); -		ensure_equals("alt recorder count", altRecorder->countMessages(), 1); -		ensure_contains("alt recorder message 0", altRecorder->message(0), "boo"); +		ensure_equals("alt recorder count", boost::dynamic_pointer_cast<TestRecorder>(altRecorder)->countMessages(), 1); +		ensure_contains("alt recorder message 0", boost::dynamic_pointer_cast<TestRecorder>(altRecorder)->message(0), "boo");  		LLError::setTimeFunction(roswell); -		TestRecorder* anotherRecorder(new TestRecorder); -		anotherRecorder->setWantsTime(true); +		LLError::RecorderPtr anotherRecorder(new TestRecorder()); +		boost::dynamic_pointer_cast<TestRecorder>(anotherRecorder)->setWantsTime(true);  		LLError::addRecorder(anotherRecorder);  		LL_INFOS() << "baz" << LL_ENDL; @@ -579,10 +594,13 @@ namespace tut  		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); +		ensure_equals("alt recorder count", boost::dynamic_pointer_cast<TestRecorder>(altRecorder)->countMessages(), 2); +		ensure_does_not_contain("alt recorder message 1", boost::dynamic_pointer_cast<TestRecorder>(altRecorder)->message(1), when); +		ensure_equals("another recorder count", boost::dynamic_pointer_cast<TestRecorder>(anotherRecorder)->countMessages(), 1); +		ensure_contains("another recorder message 0", boost::dynamic_pointer_cast<TestRecorder>(anotherRecorder)->message(0), when); + +		LLError::removeRecorder(altRecorder); +		LLError::removeRecorder(anotherRecorder);  	}  } diff --git a/indra/llcommon/tests/wrapllerrs.h b/indra/llcommon/tests/wrapllerrs.h index 3137bd8fea..0a52844671 100755 --- a/indra/llcommon/tests/wrapllerrs.h +++ b/indra/llcommon/tests/wrapllerrs.h @@ -38,6 +38,7 @@  #include "stringize.h"  #include <boost/bind.hpp>  #include <boost/noncopyable.hpp> +#include <boost/shared_ptr.hpp>  #include <list>  #include <string>  #include <stdexcept> @@ -86,67 +87,24 @@ struct WrapLLErrs  };  /** - * LLError::addRecorder() accepts ownership of the passed Recorder* -- it - * expects to be able to delete it later. CaptureLog isa Recorder whose - * pointer we want to be able to pass without any ownership implications. - * For such cases, instantiate a new RecorderProxy(yourRecorder) and pass - * that. Your heap RecorderProxy might later be deleted, but not yourRecorder. - */ -class RecorderProxy: public LLError::Recorder -{ -public: -    RecorderProxy(LLError::Recorder* recorder): -        mRecorder(recorder) -    {} - -    virtual void recordMessage(LLError::ELevel level, const std::string& message) -    { -        mRecorder->recordMessage(level, message); -    } - -    virtual bool wantsTime() -    { -        return mRecorder->wantsTime(); -    } - -private: -    LLError::Recorder* mRecorder; -}; - -/**   * Capture log messages. This is adapted (simplified) from the one in   * llerror_test.cpp.   */ -class CaptureLog : public LLError::Recorder, public boost::noncopyable +class CaptureLogRecorder : public LLError::Recorder, public boost::noncopyable  {  public: -    CaptureLog(LLError::ELevel level=LLError::LEVEL_DEBUG): -        // Mostly what we're trying to accomplish by saving and resetting -        // LLError::Settings is to bypass the default RecordToStderr and -        // RecordToWinDebug Recorders. As these are visible only inside -        // llerror.cpp, we can't just call LLError::removeRecorder() with -        // each. For certain tests we need to produce, capture and examine -        // DEBUG log messages -- but we don't want to spam the user's console -        // with that output. If it turns out that saveAndResetSettings() has -        // some bad effect, give up and just let the DEBUG level log messages -        // display. -        mOldSettings(LLError::saveAndResetSettings()), -        mProxy(new RecorderProxy(this)) +    CaptureLogRecorder() +		: LLError::Recorder(), +		boost::noncopyable(), +		mMessages()      { -        LLError::setFatalFunction(wouldHaveCrashed); -        LLError::setDefaultLevel(level); -        LLError::addRecorder(mProxy);      } -    ~CaptureLog() +    virtual ~CaptureLogRecorder()      { -        LLError::removeRecorder(mProxy); -        delete mProxy; -        LLError::restoreSettings(mOldSettings);      } -    void recordMessage(LLError::ELevel level, -                       const std::string& message) +    virtual void recordMessage(LLError::ELevel level, const std::string& message)      {          mMessages.push_back(message);      } @@ -154,7 +112,7 @@ public:      /// Don't assume the message we want is necessarily the LAST log message      /// emitted by the underlying code; search backwards through all messages      /// for the sought string. -    std::string messageWith(const std::string& search, bool required=true) +    std::string messageWith(const std::string& search, bool required)      {          for (MessageList::const_reverse_iterator rmi(mMessages.rbegin()), rmend(mMessages.rend());               rmi != rmend; ++rmi) @@ -187,14 +145,63 @@ public:          return out;      } +private:      typedef std::list<std::string> MessageList;      MessageList mMessages; +}; + +/** + * Capture log messages. This is adapted (simplified) from the one in + * llerror_test.cpp. + */ +class CaptureLog : public boost::noncopyable +{ +public: +    CaptureLog(LLError::ELevel level=LLError::LEVEL_DEBUG) +        // Mostly what we're trying to accomplish by saving and resetting +        // LLError::Settings is to bypass the default RecordToStderr and +        // RecordToWinDebug Recorders. As these are visible only inside +        // llerror.cpp, we can't just call LLError::removeRecorder() with +        // each. For certain tests we need to produce, capture and examine +        // DEBUG log messages -- but we don't want to spam the user's console +        // with that output. If it turns out that saveAndResetSettings() has +        // some bad effect, give up and just let the DEBUG level log messages +        // display. +		: boost::noncopyable(), +        mOldSettings(LLError::saveAndResetSettings()), +		mRecorder(new CaptureLogRecorder()) +    { +        LLError::setFatalFunction(wouldHaveCrashed); +        LLError::setDefaultLevel(level); +        LLError::addRecorder(mRecorder); +    } + +    ~CaptureLog() +    { +        LLError::removeRecorder(mRecorder); +        LLError::restoreSettings(mOldSettings); +    } + +    /// Don't assume the message we want is necessarily the LAST log message +    /// emitted by the underlying code; search backwards through all messages +    /// for the sought string. +    std::string messageWith(const std::string& search, bool required=true) +    { +		return boost::dynamic_pointer_cast<CaptureLogRecorder>(mRecorder)->messageWith(search, required); +    } + +    std::ostream& streamto(std::ostream& out) const +    { +		return boost::dynamic_pointer_cast<CaptureLogRecorder>(mRecorder)->streamto(out); +    } + +private:      LLError::Settings* mOldSettings; -    LLError::Recorder* mProxy; +	LLError::RecorderPtr mRecorder;  };  inline -std::ostream& operator<<(std::ostream& out, const CaptureLog& log) +std::ostream& operator<<(std::ostream& out, const CaptureLogRecorder& log)  {      return log.streamto(out);  } diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index 2cb8e6a3ab..8e5d577968 100755 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -261,7 +261,7 @@ std::string	LLViewerWindow::sMovieBaseName;  LLTrace::SampleStatHandle<> LLViewerWindow::sMouseVelocityStat("Mouse Velocity"); -class RecordToChatConsole : public LLError::Recorder, public LLSingleton<RecordToChatConsole> +class RecordToChatConsoleRecorder : public LLError::Recorder  {  public:  	virtual void recordMessage(LLError::ELevel level, @@ -285,6 +285,22 @@ public:  	}  }; +class RecordToChatConsole : public LLSingleton<RecordToChatConsole> +{ +public: +	RecordToChatConsole() +		: LLSingleton<RecordToChatConsole>(), +		mRecorder(new RecordToChatConsoleRecorder()) +	{ +	} + +	void startRecorder() { LLError::addRecorder(mRecorder); } +	void stopRecorder() { LLError::removeRecorder(mRecorder); } + +private: +	LLError::RecorderPtr mRecorder; +}; +  ////////////////////////////////////////////////////////////////////////////  //  // LLDebugText @@ -1889,11 +1905,11 @@ void LLViewerWindow::initBase()  	// optionally forward warnings to chat console/chat floater  	// for qa runs and dev builds  #if  !LL_RELEASE_FOR_DOWNLOAD -	LLError::addRecorder(RecordToChatConsole::getInstance()); +	RecordToChatConsole::getInstance()->startRecorder();  #else  	if(gSavedSettings.getBOOL("QAMode"))  	{ -		LLError::addRecorder(RecordToChatConsole::getInstance()); +		RecordToChatConsole::getInstance()->startRecorder();  	}  #endif @@ -2040,8 +2056,7 @@ void LLViewerWindow::initWorldUI()  void LLViewerWindow::shutdownViews()  {  	// clean up warning logger -	LLError::removeRecorder(RecordToChatConsole::getInstance()); - +	RecordToChatConsole::getInstance()->stopRecorder();  	LL_INFOS() << "Warning logger is cleaned." << LL_ENDL ;  	delete mDebugText; diff --git a/indra/test/test.cpp b/indra/test/test.cpp index 10f71a2843..0dd21b6a05 100755 --- a/indra/test/test.cpp +++ b/indra/test/test.cpp @@ -95,25 +95,20 @@ public:  	virtual void replay(std::ostream&) {}  }; -class LLReplayLogReal: public LLReplayLog, public LLError::Recorder, public boost::noncopyable +class RecordToTempFile : public LLError::Recorder, public boost::noncopyable  {  public: -	LLReplayLogReal(LLError::ELevel level, apr_pool_t* pool): -		mOldSettings(LLError::saveAndResetSettings()), -		mProxy(new RecorderProxy(this)), -		mTempFile("log", "", pool),		// create file -		mFile(mTempFile.getName().c_str()) // open it +	RecordToTempFile(apr_pool_t* pPool) +		: LLError::Recorder(), +		boost::noncopyable(), +		mTempFile("log", "", pPool), +		mFile(mTempFile.getName().c_str())  	{ -		LLError::setFatalFunction(wouldHaveCrashed); -		LLError::setDefaultLevel(level); -		LLError::addRecorder(mProxy);  	} -	virtual ~LLReplayLogReal() +	virtual ~RecordToTempFile()  	{ -		LLError::removeRecorder(mProxy); -		delete mProxy; -		LLError::restoreSettings(mOldSettings); +		mFile.close();  	}  	virtual void recordMessage(LLError::ELevel level, const std::string& message) @@ -121,13 +116,13 @@ public:  		mFile << message << std::endl;  	} -	virtual void reset() +	void reset()  	{  		mFile.close();  		mFile.open(mTempFile.getName().c_str());  	} -	virtual void replay(std::ostream& out) +	void replay(std::ostream& out)  	{  		mFile.close();  		std::ifstream inf(mTempFile.getName().c_str()); @@ -139,12 +134,45 @@ public:  	}  private: -	LLError::Settings* mOldSettings; -	LLError::Recorder* mProxy;  	NamedTempFile mTempFile;  	std::ofstream mFile;  }; +class LLReplayLogReal: public LLReplayLog, public boost::noncopyable +{ +public: +	LLReplayLogReal(LLError::ELevel level, apr_pool_t* pool) +		: LLReplayLog(), +		boost::noncopyable(), +		mOldSettings(LLError::saveAndResetSettings()), +		mRecorder(new RecordToTempFile(pool)) +	{ +		LLError::setFatalFunction(wouldHaveCrashed); +		LLError::setDefaultLevel(level); +		LLError::addRecorder(mRecorder); +	} + +	virtual ~LLReplayLogReal() +	{ +		LLError::removeRecorder(mRecorder); +		LLError::restoreSettings(mOldSettings); +	} + +	virtual void reset() +	{ +		boost::dynamic_pointer_cast<RecordToTempFile>(mRecorder)->reset(); +	} + +	virtual void replay(std::ostream& out) +	{ +		boost::dynamic_pointer_cast<RecordToTempFile>(mRecorder)->replay(out); +	} + +private: +	LLError::Settings* mOldSettings; +	LLError::RecorderPtr mRecorder; +}; +  class LLTestCallback : public tut::callback  {  public: | 
