diff options
| author | Dave Parks <davep@lindenlab.com> | 2012-06-28 13:50:35 -0500 | 
|---|---|---|
| committer | Dave Parks <davep@lindenlab.com> | 2012-06-28 13:50:35 -0500 | 
| commit | db5d1b851a0d808dc1e8b0896fad734c2c54a03f (patch) | |
| tree | ebe50ddb5e1aa700d54f50bbc1ab4b435462b85e /indra/test | |
| parent | 1d8f117069945499ac297ef13eb6a916a2b96d72 (diff) | |
| parent | ed72fd0ae98671f1cfce3c975b93e1f760fc40f0 (diff) | |
merge
Diffstat (limited to 'indra/test')
| -rw-r--r-- | indra/test/CMakeLists.txt | 1 | ||||
| -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/llhttpclient_tut.cpp | 388 | ||||
| -rw-r--r-- | indra/test/manageapr.h | 46 | ||||
| -rw-r--r-- | indra/test/namedtempfile.h | 205 | ||||
| -rw-r--r-- | indra/test/test.cpp | 144 | 
7 files changed, 472 insertions, 476 deletions
diff --git a/indra/test/CMakeLists.txt b/indra/test/CMakeLists.txt index 328ab4ca51..816f1d7175 100644 --- a/indra/test/CMakeLists.txt +++ b/indra/test/CMakeLists.txt @@ -37,7 +37,6 @@ set(test_SOURCE_FILES      lldoubledispatch_tut.cpp      llevents_tut.cpp      llhttpdate_tut.cpp -    llhttpclient_tut.cpp      llhttpnode_tut.cpp      lliohttpserver_tut.cpp      llmessageconfig_tut.cpp 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/llhttpclient_tut.cpp b/indra/test/llhttpclient_tut.cpp deleted file mode 100644 index 29a0002abc..0000000000 --- a/indra/test/llhttpclient_tut.cpp +++ /dev/null @@ -1,388 +0,0 @@ -/**  - * @file llhttpclient_tut.cpp - * @brief Testing the HTTP client classes. - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - *  - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - *  - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU - * Lesser General Public License for more details. - *  - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA - *  - * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA - * $/LicenseInfo$ - */ - -/** - * - * These classes test the HTTP client framework. - * - */ - -#include <tut/tut.hpp> -#include "linden_common.h" - -// These are too slow on Windows to actually include in the build. JC -#if !LL_WINDOWS - -#include "lltut.h" -#include "llhttpclient.h" -#include "llformat.h" -#include "llpipeutil.h" -#include "llproxy.h" -#include "llpumpio.h" - -#include "llsdhttpserver.h" -#include "lliohttpserver.h" -#include "lliosocket.h" - -namespace tut -{ -	LLSD storage; -	 -	class LLSDStorageNode : public LLHTTPNode -	{ -	public: -		LLSD simpleGet() const					{ return storage; } -		LLSD simplePut(const LLSD& value) const	{ storage = value; return LLSD(); } -	}; - -	class ErrorNode : public LLHTTPNode -	{ -	public: -		void get(ResponsePtr r, const LLSD& context) const -			{ r->status(599, "Intentional error"); } -		void post(ResponsePtr r, const LLSD& context, const LLSD& input) const -			{ r->status(input["status"], input["reason"]); } -	}; - -	class TimeOutNode : public LLHTTPNode -	{ -	public: -		void get(ResponsePtr r, const LLSD& context) const -		{ -            /* do nothing, the request will eventually time out */  -		} -	}; - -	LLHTTPRegistration<LLSDStorageNode> gStorageNode("/test/storage"); -	LLHTTPRegistration<ErrorNode>		gErrorNode("/test/error"); -	LLHTTPRegistration<TimeOutNode>		gTimeOutNode("/test/timeout"); - -	struct HTTPClientTestData -	{ -	public: -		HTTPClientTestData() -		{ -			apr_pool_create(&mPool, NULL); -			LLCurl::initClass(false); -			mServerPump = new LLPumpIO(mPool); -			mClientPump = new LLPumpIO(mPool); - -			LLHTTPClient::setPump(*mClientPump); -		} -		 -		~HTTPClientTestData() -		{ -			delete mServerPump; -			delete mClientPump; -			LLProxy::cleanupClass(); -			apr_pool_destroy(mPool); -		} - -		void setupTheServer() -		{ -			LLHTTPNode& root = LLIOHTTPServer::create(mPool, *mServerPump, 8888); - -			LLHTTPStandardServices::useServices(); -			LLHTTPRegistrar::buildAllServices(root); -		} -		 -		void runThePump(float timeout = 100.0f) -		{ -			LLTimer timer; -			timer.setTimerExpirySec(timeout); - -			while(!mSawCompleted && !mSawCompletedHeader && !timer.hasExpired()) -			{ -				if (mServerPump) -				{ -					mServerPump->pump(); -					mServerPump->callback(); -				} -				if (mClientPump) -				{ -					mClientPump->pump(); -					mClientPump->callback(); -				} -			} -		} - -		void killServer() -		{ -			delete mServerPump; -			mServerPump = NULL; -		} -	 -	private: -		apr_pool_t* mPool; -		LLPumpIO* mServerPump; -		LLPumpIO* mClientPump; - -		 -	protected: -		void ensureStatusOK() -		{ -			if (mSawError) -			{ -				std::string msg = -					llformat("error() called when not expected, status %d", -						mStatus);  -				fail(msg); -			} -		} -	 -		void ensureStatusError() -		{ -			if (!mSawError) -			{ -				fail("error() wasn't called"); -			} -		} -		 -		LLSD getResult() -		{ -			return mResult; -		} -		LLSD getHeader() -		{ -			return mHeader; -		} -	 -	protected: -		bool mSawError; -		U32 mStatus; -		std::string mReason; -		bool mSawCompleted; -		bool mSawCompletedHeader; -		LLSD mResult; -		LLSD mHeader; -		bool mResultDeleted; - -		class Result : public LLHTTPClient::Responder -		{ -		protected: -			Result(HTTPClientTestData& client) -				: mClient(client) -			{ -			} -		 -		public: -			static boost::intrusive_ptr<Result> build(HTTPClientTestData& client) -			{ -				return boost::intrusive_ptr<Result>(new Result(client)); -			} -			 -			~Result() -			{ -				mClient.mResultDeleted = true; -			} -			 -			virtual void error(U32 status, const std::string& reason) -			{ -				mClient.mSawError = true; -				mClient.mStatus = status; -				mClient.mReason = reason; -			} - -			virtual void result(const LLSD& content) -			{ -				mClient.mResult = content; -			} - -			virtual void completed( -							U32 status, const std::string& reason, -							const LLSD& content) -			{ -				LLHTTPClient::Responder::completed(status, reason, content); -				 -				mClient.mSawCompleted = true; -			} - -			virtual void completedHeader( -				U32 status, const std::string& reason, -				const LLSD& content) -			{ -				mClient.mHeader = content; -				mClient.mSawCompletedHeader = true; -			} - -		private: -			HTTPClientTestData& mClient; -		}; - -		friend class Result; - -	protected: -		LLHTTPClient::ResponderPtr newResult() -		{ -			mSawError = false; -			mStatus = 0; -			mSawCompleted = false; -			mSawCompletedHeader = false; -			mResult.clear(); -			mHeader.clear(); -			mResultDeleted = false; -			 -			return Result::build(*this); -		} -	}; -	 -	 -	typedef test_group<HTTPClientTestData>	HTTPClientTestGroup; -	typedef HTTPClientTestGroup::object		HTTPClientTestObject; -	HTTPClientTestGroup httpClientTestGroup("http_client"); - -	template<> template<> -	void HTTPClientTestObject::test<1>() -	{ -		skip("google.com unit tests fail."); - -		LLHTTPClient::get("http://www.google.com/", newResult()); -		runThePump(); -		ensureStatusOK(); -		ensure("result object wasn't destroyed", mResultDeleted); -	} - -	template<> template<> -	void HTTPClientTestObject::test<2>() -	{ -		skip("error test depends on dev's local ISP not supplying \"helpful\" search page"); -		LLHTTPClient::get("http://www.invalid", newResult()); -		runThePump(); -		ensureStatusError(); -	} - -	template<> template<> -		void HTTPClientTestObject::test<3>() -	{ -		LLSD sd; - -		sd["list"][0]["one"] = 1; -		sd["list"][0]["two"] = 2; -		sd["list"][1]["three"] = 3; -		sd["list"][1]["four"] = 4; -		 -		setupTheServer(); - -		LLHTTPClient::post("http://localhost:8888/web/echo", sd, newResult()); -		runThePump(); -		ensureStatusOK(); -		ensure_equals("echoed result matches", getResult(), sd); -	} - -	template<> template<> -		void HTTPClientTestObject::test<4>() -	{ -		LLSD sd; - -		sd["message"] = "This is my test message."; - -		setupTheServer(); -		LLHTTPClient::put("http://localhost:8888/test/storage", sd, newResult()); -		runThePump(); -		ensureStatusOK(); - -		LLHTTPClient::get("http://localhost:8888/test/storage", newResult()); -		runThePump(); -		ensureStatusOK(); -		ensure_equals("echoed result matches", getResult(), sd); -	 -	} - -	template<> template<> -		void HTTPClientTestObject::test<5>() -	{ -		LLSD sd; -		sd["status"] = 543; -		sd["reason"] = "error for testing"; - -		setupTheServer(); - -		LLHTTPClient::post("http://localhost:8888/test/error", sd, newResult()); -		runThePump(); -		ensureStatusError(); -		ensure_contains("reason", mReason, sd["reason"]); -	} - -	template<> template<> -		void HTTPClientTestObject::test<6>() -	{ -		setupTheServer(); - -		LLHTTPClient::get("http://localhost:8888/test/timeout", newResult()); -		runThePump(1.0f); -		killServer(); -		runThePump(); -		ensureStatusError(); -		ensure_equals("reason", mReason, "STATUS_ERROR"); -	} - -	template<> template<> -		void HTTPClientTestObject::test<7>() -	{ -		// Can not use the little mini server.  The blocking request -		// won't ever let it run.  Instead get from a known LLSD -		// source and compare results with the non-blocking get which -		// is tested against the mini server earlier. -		skip("secondlife.com is not reliable enough for unit tests."); - - -		LLSD expected; - -		LLHTTPClient::get("http://secondlife.com/xmlhttp/homepage.php", newResult()); -		runThePump(); -		ensureStatusOK(); -		expected = getResult(); - -		LLSD result; -		result = LLHTTPClient::blockingGet("http://secondlife.com/xmlhttp/homepage.php"); -		LLSD body = result["body"]; -		ensure_equals("echoed result matches", body.size(), expected.size()); -	} -	template<> template<> -		void HTTPClientTestObject::test<8>() -	{ -		skip("google.com unit tests fail."); - -		// This is testing for the presence of the Header in the returned results -		// from an HTTP::get call. -		LLHTTPClient::get("http://www.google.com/", newResult()); -		runThePump(); -		ensureStatusOK(); -		LLSD header = getHeader(); -		ensure_equals("got a header", header.emptyMap().asBoolean(), false); -	} -	template<> template<> -	void HTTPClientTestObject::test<9>() -	{ -		skip("google.com unit tests fail."); -		LLHTTPClient::head("http://www.google.com/", newResult()); -		runThePump(); -		ensureStatusOK(); -		ensure("result object wasn't destroyed", mResultDeleted); -	} -} - -#endif	// !LL_WINDOWS 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..9d24383bcc 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" @@ -70,25 +72,91 @@  #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) : -	mVerboseMode(verbose_mode), -	mTotalTests(0), -	mPassedTests(0), -	mFailedTests(0), -	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)) +	LLTestCallback(bool verbose_mode, std::ostream *stream, +				   boost::shared_ptr<LLReplayLog> replayer) : +		mVerboseMode(verbose_mode), +		mTotalTests(0), +		mPassedTests(0), +		mFailedTests(0), +		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)), +		mReplayer(replayer)  	{  		if (stream)  		{ @@ -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.get(), replayer);  	}  	else  	{ -		mycallback = new LLTestCallback(verbose_mode, output.get()); +		mycallback = new LLTestCallback(verbose_mode, output.get(), replayer);  	}  	tut::runner.get().set_callback(mycallback);  | 
