diff options
Diffstat (limited to 'indra/test')
| -rw-r--r-- | indra/test/catch_and_store_what_in.h | 86 | ||||
| -rw-r--r-- | indra/test/llevents_tut.cpp | 78 | ||||
| -rw-r--r-- | indra/test/manageapr.h | 46 | ||||
| -rw-r--r-- | indra/test/namedtempfile.h | 205 | ||||
| -rw-r--r-- | indra/test/test.cpp | 134 | 
5 files changed, 467 insertions, 82 deletions
diff --git a/indra/test/catch_and_store_what_in.h b/indra/test/catch_and_store_what_in.h new file mode 100644 index 0000000000..59f8cc0085 --- /dev/null +++ b/indra/test/catch_and_store_what_in.h @@ -0,0 +1,86 @@ +/** + * @file   catch_and_store_what_in.h + * @author Nat Goodspeed + * @date   2012-02-15 + * @brief  CATCH_AND_STORE_WHAT_IN() macro + *  + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Copyright (c) 2012, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_CATCH_AND_STORE_WHAT_IN_H) +#define LL_CATCH_AND_STORE_WHAT_IN_H + +/** + * Idiom useful for test programs: catch an expected exception, store its + * what() string in a specified std::string variable. From there the caller + * can do things like: + * @code + * ensure("expected exception not thrown", ! string.empty()); + * @endcode + * or + * @code + * ensure_contains("exception doesn't mention blah", string, "blah"); + * @endcode + * etc. + * + * The trouble is that when linking to a dynamic libllcommon.so on Linux, we + * generally fail to catch the specific exception. Oddly, we can catch it as + * std::runtime_error and validate its typeid().name(), so we do -- but that's + * a lot of boilerplate per test. Encapsulate with this macro. Usage: + * + * @code + * std::string threw; + * try + * { + *     some_call_that_should_throw_Foo(); + * } + * CATCH_AND_STORE_WHAT_IN(threw, Foo) + * ensure("some_call_that_should_throw_Foo() didn't throw", ! threw.empty()); + * @endcode + */ +#define CATCH_AND_STORE_WHAT_IN(THREW, EXCEPTION)	\ +catch (const EXCEPTION& ex)							\ +{													\ +	(THREW) = ex.what();							\ +}													\ +CATCH_MISSED_LINUX_EXCEPTION(THREW, EXCEPTION) + +#ifndef LL_LINUX +#define CATCH_MISSED_LINUX_EXCEPTION(THREW, EXCEPTION)					\ +	/* only needed on Linux */ +#else // LL_LINUX + +#define CATCH_MISSED_LINUX_EXCEPTION(THREW, EXCEPTION)					\ +catch (const std::runtime_error& ex)									\ +{																		\ +	/* This clause is needed on Linux, on the viewer side, because	*/	\ +	/* the exception isn't caught by catch (const EXCEPTION&).		*/	\ +	/* But if the expected exception was thrown, allow the test to	*/	\ +	/* succeed anyway. Not sure how else to handle this odd case.	*/	\ +	if (std::string(typeid(ex).name()) == typeid(EXCEPTION).name())		\ +	{																	\ +		/* std::cerr << "Caught " << typeid(ex).name() */				\ +		/*			 << " with Linux workaround" << std::endl; */		\ +		(THREW) = ex.what();											\ +		/*std::cout << ex.what() << std::endl;*/						\ +	}																	\ +	else																\ +	{																	\ +		/* We don't even recognize this exception. Let it propagate	*/	\ +		/* out to TUT to fail the test.								*/	\ +		throw;															\ +	}																	\ +}																		\ +catch (...)																\ +{																		\ +	std::cerr << "Failed to catch expected exception "					\ +			  << #EXCEPTION << "!" << std::endl;						\ +	/* This indicates a problem in the test that should be addressed. */ \ +	throw;																\ +} + +#endif // LL_LINUX + +#endif /* ! defined(LL_CATCH_AND_STORE_WHAT_IN_H) */ diff --git a/indra/test/llevents_tut.cpp b/indra/test/llevents_tut.cpp index 4699bb1827..a9114075fc 100644 --- a/indra/test/llevents_tut.cpp +++ b/indra/test/llevents_tut.cpp @@ -49,46 +49,12 @@  #include <boost/assign/list_of.hpp>  // other Linden headers  #include "lltut.h" +#include "catch_and_store_what_in.h"  #include "stringize.h"  #include "tests/listener.h"  using boost::assign::list_of; -#ifdef LL_LINUX -#define CATCH_MISSED_LINUX_EXCEPTION(exception, threw)										\ -catch (const std::runtime_error& ex)														\ -{																							\ -	/* This clause is needed on Linux, on the viewer side, because the	*/					\ -	/* exception isn't caught by the clause above. Warn the user...		*/					\ -	std::cerr << "Failed to catch " << typeid(ex).name() << std::endl;						\ -	/* But if the expected exception was thrown, allow the test to		*/					\ -	/* succeed anyway. Not sure how else to handle this odd case.		*/					\ -	/* This approach is also used in llsdmessage_test.cpp. 				*/					\ -	if (std::string(typeid(ex).name()) == typeid(exception).name())							\ -	{																						\ -		threw = ex.what();																	\ -		/*std::cout << ex.what() << std::endl;*/											\ -	}																						\ -	else																					\ -	{																						\ -		/* We don't even recognize this exception. Let it propagate		*/					\ -		/* out to TUT to fail the test.									*/					\ -		throw;																				\ -	}																						\ -}																							\ -catch (...)																					\ -{																							\ -	std::cerr << "Utterly failed to catch expected exception " << #exception << "!" <<		\ -	std::endl;																				\ -	/* This indicates a problem in the test that should be addressed.   */					\ -	throw;																					\ -} - -#else // LL_LINUX -#define CATCH_MISSED_LINUX_EXCEPTION(exception, threw)										\ -	/* Not needed on other platforms */ -#endif // LL_LINUX -  template<typename T>  T make(const T& value)  { @@ -178,11 +144,7 @@ void events_object::test<1>()  		per_frame.listen(listener0.getName(), // note bug, dup name  						 boost::bind(&Listener::call, boost::ref(listener1), _1));  	} -	catch (const LLEventPump::DupListenerName& e) -	{ -		threw = e.what(); -	} -	CATCH_MISSED_LINUX_EXCEPTION(LLEventPump::DupListenerName, threw) +	CATCH_AND_STORE_WHAT_IN(threw, LLEventPump::DupListenerName)  	ensure_equals(threw,  				  std::string("DupListenerName: "  							  "Attempt to register duplicate listener name '") + @@ -354,7 +316,6 @@ void events_object::test<7>()  {  	set_test_name("listener dependency order");  	typedef LLEventPump::NameList NameList; -	typedef Collect::StringList StringList;  	LLEventPump& button(pumps.obtain("button"));  	Collect collector;  	button.listen("Mary", @@ -368,7 +329,7 @@ void events_object::test<7>()  	button.listen("spot",  				  boost::bind(&Collect::add, boost::ref(collector), "spot", _1));  	button.post(1); -	ensure_equals(collector.result, make<StringList>(list_of("spot")("checked")("Mary"))); +	ensure_equals(collector.result, make<StringVec>(list_of("spot")("checked")("Mary")));  	collector.clear();  	button.stopListening("Mary");  	button.listen("Mary", @@ -377,7 +338,7 @@ void events_object::test<7>()  			// now "Mary" must come before "spot"  			make<NameList>(list_of("spot")));  	button.post(2); -	ensure_equals(collector.result, make<StringList>(list_of("Mary")("spot")("checked"))); +	ensure_equals(collector.result, make<StringVec>(list_of("Mary")("spot")("checked")));  	collector.clear();  	button.stopListening("spot");  	std::string threw; @@ -388,12 +349,7 @@ void events_object::test<7>()  					  // after "Mary" and "checked" -- whoops!  			 		  make<NameList>(list_of("Mary")("checked")));  	} -	catch (const LLEventPump::Cycle& e) -	{ -		threw = e.what(); -		// std::cout << "Caught: " << e.what() << '\n'; -	} -	CATCH_MISSED_LINUX_EXCEPTION(LLEventPump::Cycle, threw) +	CATCH_AND_STORE_WHAT_IN(threw, LLEventPump::Cycle)  	// Obviously the specific wording of the exception text can  	// change; go ahead and change the test to match.  	// Establish that it contains: @@ -416,7 +372,7 @@ void events_object::test<7>()  				  boost::bind(&Collect::add, boost::ref(collector), "shoelaces", _1),  				  make<NameList>(list_of("checked")));  	button.post(3); -	ensure_equals(collector.result, make<StringList>(list_of("Mary")("checked")("yellow")("shoelaces"))); +	ensure_equals(collector.result, make<StringVec>(list_of("Mary")("checked")("yellow")("shoelaces")));  	collector.clear();  	threw.clear();  	try @@ -426,12 +382,7 @@ void events_object::test<7>()  					  make<NameList>(list_of("shoelaces")),  					  make<NameList>(list_of("yellow")));  	} -	catch (const LLEventPump::OrderChange& e) -	{ -		threw = e.what(); -		// std::cout << "Caught: " << e.what() << '\n'; -	} -	CATCH_MISSED_LINUX_EXCEPTION(LLEventPump::OrderChange, threw) +	CATCH_AND_STORE_WHAT_IN(threw, LLEventPump::OrderChange)  	// Same remarks about the specific wording of the exception. Just  	// ensure that it contains enough information to clarify the  	// problem and what must be done to resolve it. @@ -443,7 +394,7 @@ void events_object::test<7>()  	ensure_contains("old order", threw, "was: Mary, checked, yellow, shoelaces");  	ensure_contains("new order", threw, "now: Mary, checked, shoelaces, of, yellow");  	button.post(4); -	ensure_equals(collector.result, make<StringList>(list_of("Mary")("checked")("yellow")("shoelaces"))); +	ensure_equals(collector.result, make<StringVec>(list_of("Mary")("checked")("yellow")("shoelaces")));  }  template<> template<> @@ -459,12 +410,7 @@ void events_object::test<8>()  			// then another with a duplicate name.  			LLEventStream bob2("bob");  		} -		catch (const LLEventPump::DupPumpName& e) -		{ -			threw = e.what(); -			// std::cout << "Caught: " << e.what() << '\n'; -		} -		CATCH_MISSED_LINUX_EXCEPTION(LLEventPump::DupPumpName, threw) +		CATCH_AND_STORE_WHAT_IN(threw, LLEventPump::DupPumpName)  		ensure("Caught DupPumpName", !threw.empty());  	} 	// delete first 'bob'  	LLEventStream bob("bob"); 		// should work, previous one unregistered @@ -505,11 +451,7 @@ void events_object::test<9>()  		LLListenerOrPumpName empty;  		empty(17);  	} -	catch (const LLListenerOrPumpName::Empty& e) -	{ -		threw = e.what(); -	} -	CATCH_MISSED_LINUX_EXCEPTION(LLListenerOrPumpName::Empty, threw) +	CATCH_AND_STORE_WHAT_IN(threw, LLListenerOrPumpName::Empty)  	ensure("threw Empty", !threw.empty());  } diff --git a/indra/test/manageapr.h b/indra/test/manageapr.h new file mode 100644 index 0000000000..2452fb6ae4 --- /dev/null +++ b/indra/test/manageapr.h @@ -0,0 +1,46 @@ +/** + * @file   manageapr.h + * @author Nat Goodspeed + * @date   2012-01-13 + * @brief  ManageAPR class for simple test programs + *  + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Copyright (c) 2012, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_MANAGEAPR_H) +#define LL_MANAGEAPR_H + +#include "llapr.h" +#include <boost/noncopyable.hpp> + +/** + * Declare a static instance of this class for dead-simple ll_init_apr() at + * program startup, ll_cleanup_apr() at termination. This is recommended for + * use only with simple test programs. Once you start introducing static + * instances of other classes that depend on APR already being initialized, + * the indeterminate static-constructor-order problem rears its ugly head. + */ +class ManageAPR: public boost::noncopyable +{ +public: +    ManageAPR() +    { +        ll_init_apr(); +    } + +    ~ManageAPR() +    { +        ll_cleanup_apr(); +    } + +    static std::string strerror(apr_status_t rv) +    { +        char errbuf[256]; +        apr_strerror(rv, errbuf, sizeof(errbuf)); +        return errbuf; +    } +}; + +#endif /* ! defined(LL_MANAGEAPR_H) */ diff --git a/indra/test/namedtempfile.h b/indra/test/namedtempfile.h new file mode 100644 index 0000000000..6069064627 --- /dev/null +++ b/indra/test/namedtempfile.h @@ -0,0 +1,205 @@ +/** + * @file   namedtempfile.h + * @author Nat Goodspeed + * @date   2012-01-13 + * @brief  NamedTempFile class for tests that need disk files as fixtures. + *  + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Copyright (c) 2012, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_NAMEDTEMPFILE_H) +#define LL_NAMEDTEMPFILE_H + +#include "llerror.h" +#include "llapr.h" +#include "apr_file_io.h" +#include <string> +#include <boost/function.hpp> +#include <boost/lambda/lambda.hpp> +#include <boost/lambda/bind.hpp> +#include <boost/noncopyable.hpp> +#include <iostream> +#include <sstream> + +/** + * Create a text file with specified content "somewhere in the + * filesystem," cleaning up when it goes out of scope. + */ +class NamedTempFile: public boost::noncopyable +{ +    LOG_CLASS(NamedTempFile); +public: +    NamedTempFile(const std::string& pfx, const std::string& content, apr_pool_t* pool=gAPRPoolp): +        mPool(pool) +    { +        createFile(pfx, boost::lambda::_1 << content); +    } + +    // Disambiguate when passing string literal +    NamedTempFile(const std::string& pfx, const char* content, apr_pool_t* pool=gAPRPoolp): +        mPool(pool) +    { +        createFile(pfx, boost::lambda::_1 << content); +    } + +    // Function that accepts an ostream ref and (presumably) writes stuff to +    // it, e.g.: +    // (boost::lambda::_1 << "the value is " << 17 << '\n') +    typedef boost::function<void(std::ostream&)> Streamer; + +    NamedTempFile(const std::string& pfx, const Streamer& func, apr_pool_t* pool=gAPRPoolp): +        mPool(pool) +    { +        createFile(pfx, func); +    } + +    virtual ~NamedTempFile() +    { +        ll_apr_assert_status(apr_file_remove(mPath.c_str(), mPool)); +    } + +    virtual std::string getName() const { return mPath; } + +    void peep() +    { +        std::cout << "File '" << mPath << "' contains:\n"; +        std::ifstream reader(mPath.c_str()); +        std::string line; +        while (std::getline(reader, line)) +            std::cout << line << '\n'; +        std::cout << "---\n"; +    } + +protected: +    void createFile(const std::string& pfx, const Streamer& func) +    { +        // Create file in a temporary place. +        const char* tempdir = NULL; +        ll_apr_assert_status(apr_temp_dir_get(&tempdir, mPool)); + +        // Construct a temp filename template in that directory. +        char *tempname = NULL; +        ll_apr_assert_status(apr_filepath_merge(&tempname, +                                                tempdir, +                                                (pfx + "XXXXXX").c_str(), +                                                0, +                                                mPool)); + +        // Create a temp file from that template. +        apr_file_t* fp = NULL; +        ll_apr_assert_status(apr_file_mktemp(&fp, +                                             tempname, +                                             APR_CREATE | APR_WRITE | APR_EXCL, +                                             mPool)); +        // apr_file_mktemp() alters tempname with the actual name. Not until +        // now is it valid to capture as our mPath. +        mPath = tempname; + +        // Write desired content. +        std::ostringstream out; +        // Stream stuff to it. +        func(out); + +        std::string data(out.str()); +        apr_size_t writelen(data.length()); +        ll_apr_assert_status(apr_file_write(fp, data.c_str(), &writelen)); +        ll_apr_assert_status(apr_file_close(fp)); +        llassert_always(writelen == data.length()); +    } + +    std::string mPath; +    apr_pool_t* mPool; +}; + +/** + * Create a NamedTempFile with a specified filename extension. This is useful + * when, for instance, you must be able to use the file in a Python import + * statement. + * + * A NamedExtTempFile actually has two different names. We retain the original + * no-extension name as a placeholder in the temp directory to ensure + * uniqueness; to that we link the name plus the desired extension. Naturally, + * both must be removed on destruction. + */ +class NamedExtTempFile: public NamedTempFile +{ +    LOG_CLASS(NamedExtTempFile); +public: +    NamedExtTempFile(const std::string& ext, const std::string& content, apr_pool_t* pool=gAPRPoolp): +        NamedTempFile(remove_dot(ext), content, pool), +        mLink(mPath + ensure_dot(ext)) +    { +        linkto(mLink); +    } + +    // Disambiguate when passing string literal +    NamedExtTempFile(const std::string& ext, const char* content, apr_pool_t* pool=gAPRPoolp): +        NamedTempFile(remove_dot(ext), content, pool), +        mLink(mPath + ensure_dot(ext)) +    { +        linkto(mLink); +    } + +    NamedExtTempFile(const std::string& ext, const Streamer& func, apr_pool_t* pool=gAPRPoolp): +        NamedTempFile(remove_dot(ext), func, pool), +        mLink(mPath + ensure_dot(ext)) +    { +        linkto(mLink); +    } + +    virtual ~NamedExtTempFile() +    { +        ll_apr_assert_status(apr_file_remove(mLink.c_str(), mPool)); +    } + +    // Since the caller has gone to the trouble to create the name with the +    // extension, that should be the name we return. In this class, mPath is +    // just a placeholder to ensure that future createFile() calls won't +    // collide. +    virtual std::string getName() const { return mLink; } + +    static std::string ensure_dot(const std::string& ext) +    { +        if (ext.empty()) +        { +            // What SHOULD we do when the caller makes a point of using +            // NamedExtTempFile to generate a file with a particular +            // extension, then passes an empty extension? Use just "."? That +            // sounds like a Bad Idea, especially on Windows. Treat that as a +            // coding error. +            LL_ERRS("NamedExtTempFile") << "passed empty extension" << LL_ENDL; +        } +        if (ext[0] == '.') +        { +            return ext; +        } +        return std::string(".") + ext; +    } + +    static std::string remove_dot(const std::string& ext) +    { +        std::string::size_type found = ext.find_first_not_of("."); +        if (found == std::string::npos) +        { +            return ext; +        } +        return ext.substr(found); +    } + +private: +    void linkto(const std::string& path) +    { +        // This method assumes that since mPath (without extension) is +        // guaranteed by apr_file_mktemp() to be unique, then (mPath + any +        // extension) is also unique. This is likely, though not guaranteed: +        // files could be created in the same temp directory other than by +        // this class. +        ll_apr_assert_status(apr_file_link(mPath.c_str(), path.c_str())); +    } + +    std::string mLink; +}; + +#endif /* ! defined(LL_NAMEDTEMPFILE_H) */ diff --git a/indra/test/test.cpp b/indra/test/test.cpp index e58e7293fb..48f20b2e79 100644 --- a/indra/test/test.cpp +++ b/indra/test/test.cpp @@ -37,7 +37,9 @@  #include "linden_common.h"  #include "llerrorcontrol.h"  #include "lltut.h" +#include "tests/wrapllerrs.h"             // RecorderProxy  #include "stringize.h" +#include "namedtempfile.h"  #include "apr_pools.h"  #include "apr_getopt.h" @@ -64,23 +66,87 @@  #pragma warning (pop)  #endif -#include <boost/scoped_ptr.hpp>  #include <boost/shared_ptr.hpp>  #include <boost/make_shared.hpp>  #include <boost/foreach.hpp>  #include <boost/lambda/lambda.hpp> +#include <fstream> + +void wouldHaveCrashed(const std::string& message); +  namespace tut  {  	std::string sSourceDir; -	 -    test_runner_singleton runner; + +	test_runner_singleton runner;  } +class LLReplayLog +{ +public: +	LLReplayLog() {} +	virtual ~LLReplayLog() {} + +	virtual void reset() {} +	virtual void replay(std::ostream&) {} +}; + +class LLReplayLogReal: public LLReplayLog, 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 +	{ +		LLError::setFatalFunction(wouldHaveCrashed); +		LLError::setDefaultLevel(level); +		LLError::addRecorder(mProxy); +	} + +	virtual ~LLReplayLogReal() +	{ +		LLError::removeRecorder(mProxy); +		delete mProxy; +		LLError::restoreSettings(mOldSettings); +	} + +	virtual void recordMessage(LLError::ELevel level, const std::string& message) +	{ +		mFile << message << std::endl; +	} + +	virtual void reset() +	{ +		mFile.close(); +		mFile.open(mTempFile.getName().c_str()); +	} + +	virtual void replay(std::ostream& out) +	{ +		mFile.close(); +		std::ifstream inf(mTempFile.getName().c_str()); +		std::string line; +		while (std::getline(inf, line)) +		{ +			out << line << std::endl; +		} +	} + +private: +	LLError::Settings* mOldSettings; +	LLError::Recorder* mProxy; +	NamedTempFile mTempFile; +	std::ofstream mFile; +}; +  class LLTestCallback : public tut::callback  {  public: -	LLTestCallback(bool verbose_mode, std::ostream *stream) : +	LLTestCallback(bool verbose_mode, std::ostream *stream, +				   boost::shared_ptr<LLReplayLog> replayer) :  	mVerboseMode(verbose_mode),  	mTotalTests(0),  	mPassedTests(0), @@ -88,8 +154,10 @@ public:  	mSkippedTests(0),  	// By default, capture a shared_ptr to std::cout, with a no-op "deleter"  	// so that destroying the shared_ptr makes no attempt to delete std::cout. -	mStream(boost::shared_ptr<std::ostream>(&std::cout, boost::lambda::_1)) -	{ +	mStream(boost::shared_ptr<std::ostream>(&std::cout, boost::lambda::_1)), +	mReplayer(replayer) +		if (stream) +		{  		if (stream)  		{  			// We want a boost::iostreams::tee_device that will stream to two @@ -126,6 +194,16 @@ public:  	virtual void test_completed(const tut::test_result& tr)  	{  		++mTotalTests; + +		// If this test failed, dump requested log messages BEFORE stating the +		// test result. +		if (tr.result != tut::test_result::ok && tr.result != tut::test_result::skip) +		{ +			mReplayer->replay(*mStream); +		} +		// Either way, clear stored messages in preparation for next test. +		mReplayer->reset(); +  		std::ostringstream out;  		out << "[" << tr.group << ", " << tr.test;  		if (! tr.name.empty()) @@ -206,6 +284,7 @@ protected:  	int mFailedTests;  	int mSkippedTests;  	boost::shared_ptr<std::ostream> mStream; +	boost::shared_ptr<LLReplayLog> mReplayer;  };  // TeamCity specific class which emits service messages @@ -214,8 +293,9 @@ protected:  class LLTCTestCallback : public LLTestCallback  {  public: -	LLTCTestCallback(bool verbose_mode, std::ostream *stream) : -		LLTestCallback(verbose_mode, stream) +	LLTCTestCallback(bool verbose_mode, std::ostream *stream, +					 boost::shared_ptr<LLReplayLog> replayer) : +		LLTestCallback(verbose_mode, stream, replayer)  	{  	} @@ -356,6 +436,14 @@ void stream_usage(std::ostream& s, const char* app)  		++option;  	} +	s << app << " is also sensitive to environment variables:\n" +	  << "LOGTEST=level : for all tests, emit log messages at level 'level'\n" +	  << "LOGFAIL=level : only for failed tests, emit log messages at level 'level'\n" +	  << "where 'level' is one of ALL, DEBUG, INFO, WARN, ERROR, NONE.\n" +	  << "--debug is like LOGTEST=DEBUG, but --debug overrides LOGTEST.\n" +	  << "Setting LOGFAIL overrides both LOGTEST and --debug: the only log\n" +	  << "messages you will see will be for failed tests.\n\n"; +  	s << "Examples:" << std::endl;  	s << "  " << app << " --verbose" << std::endl;  	s << "\tRun all the tests and report all results." << std::endl; @@ -392,8 +480,14 @@ int main(int argc, char **argv)  	LLError::initForApplication(".");  	LLError::setFatalFunction(wouldHaveCrashed);  	LLError::setDefaultLevel(LLError::LEVEL_ERROR); -	//< *TODO: should come from error config file. Note that we -	// have a command line option that sets this to debug. +	// ^ possibly overridden by --debug, LOGTEST or LOGFAIL + +	// LOGTEST overrides default, but can be overridden by --debug or LOGFAIL. +	const char* LOGTEST = getenv("LOGTEST"); +	if (LOGTEST) +	{ +		LLError::setDefaultLevel(LLError::decodeLevel(LOGTEST)); +	}  #ifdef CTYPE_WORKAROUND  	ctype_workaround(); @@ -468,8 +562,6 @@ int main(int argc, char **argv)  				wait_at_exit = true;  				break;  			case 'd': -				// *TODO: should come from error config file. We set it to -				// ERROR by default, so this allows full debug levels.  				LLError::setDefaultLevel(LLError::LEVEL_DEBUG);  				break;  			case 'x': @@ -484,14 +576,28 @@ int main(int argc, char **argv)  	// run the tests +	const char* LOGFAIL = getenv("LOGFAIL"); +	boost::shared_ptr<LLReplayLog> replayer; +	// As described in stream_usage(), LOGFAIL overrides both --debug and +	// LOGTEST. +	if (LOGFAIL) +	{ +		LLError::ELevel level = LLError::decodeLevel(LOGFAIL); +		replayer.reset(new LLReplayLogReal(level, pool)); +	} +	else +	{ +		replayer.reset(new LLReplayLog()); +	} +  	LLTestCallback* mycallback;  	if (getenv("TEAMCITY_PROJECT_NAME"))  	{ -		mycallback = new LLTCTestCallback(verbose_mode, output.get());		 +		mycallback = new LLTCTestCallback(verbose_mode, output, replayer);  	}  	else  	{ -		mycallback = new LLTestCallback(verbose_mode, output.get()); +		mycallback = new LLTestCallback(verbose_mode, output, replayer);  	}  	tut::runner.get().set_callback(mycallback);  | 
