From b6a08ad007deb855ce4d428654279206853a3b99 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 13 Jan 2012 12:41:54 -0500 Subject: Extract APR and temp-fixture-file helper code to indra/test. Specifically: Introduce ManageAPR class in indra/test/manageapr.h. This is useful for a simple test program without lots of static constructors. Extract NamedTempFile from llsdserialize_test.cpp to indra/test/ namedtempfile.h. Refactor to use APR file operations rather than platform- dependent APIs. Use NamedTempFile for llprocesslauncher_test.cpp. --- indra/test/manageapr.h | 45 ++++++++++++++++++ indra/test/namedtempfile.h | 113 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 indra/test/manageapr.h create mode 100644 indra/test/namedtempfile.h (limited to 'indra/test') diff --git a/indra/test/manageapr.h b/indra/test/manageapr.h new file mode 100644 index 0000000000..0c1ca7b7be --- /dev/null +++ b/indra/test/manageapr.h @@ -0,0 +1,45 @@ +/** + * @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" + +/** + * 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: + 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..9670d4db53 --- /dev/null +++ b/indra/test/namedtempfile.h @@ -0,0 +1,113 @@ +/** + * @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 "llapr.h" +#include "apr_file_io.h" +#include +#include +#include "boost/lambda/lambda.hpp" +#include "boost/lambda/bind.hpp" +#include +#include + +/** + * Create a text file with specified content "somewhere in the + * filesystem," cleaning up when it goes out of scope. + */ +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 Streamer; + + NamedTempFile(const std::string& pfx, const Streamer& func, apr_pool_t* pool=gAPRPoolp): + mPool(pool) + { + createFile(pfx, func); + } + + ~NamedTempFile() + { + ll_apr_assert_status(apr_file_remove(mPath.c_str(), mPool)); + } + + std::string getName() const { return mPath; } + +private: + 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()); + } + + 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"; + } + + std::string mPath; + apr_pool_t* mPool; +}; + +#endif /* ! defined(LL_NAMEDTEMPFILE_H) */ -- cgit v1.2.3 From a01dd3549cca620de47fae824198473c51a12f49 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 17 Jan 2012 18:40:05 -0500 Subject: Make NamedTempFile::peep() a public member for debugging unit tests. --- indra/test/namedtempfile.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'indra/test') diff --git a/indra/test/namedtempfile.h b/indra/test/namedtempfile.h index 9670d4db53..7ffb2836cc 100644 --- a/indra/test/namedtempfile.h +++ b/indra/test/namedtempfile.h @@ -59,6 +59,16 @@ public: 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"; + } + private: void createFile(const std::string& pfx, const Streamer& func) { @@ -96,16 +106,6 @@ private: llassert_always(writelen == data.length()); } - 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"; - } - std::string mPath; apr_pool_t* mPool; }; -- cgit v1.2.3 From 51b26cab9ad8dc54277c6158ad40afdf3ed0e6d0 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 17 Jan 2012 20:30:46 -0500 Subject: Any proper RAII class must either handle copying or be noncopyable. NamedTempFile makes no attempt to deal with copying, therefore make it noncopyable. --- indra/test/namedtempfile.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'indra/test') diff --git a/indra/test/namedtempfile.h b/indra/test/namedtempfile.h index 7ffb2836cc..aa7058b111 100644 --- a/indra/test/namedtempfile.h +++ b/indra/test/namedtempfile.h @@ -16,8 +16,9 @@ #include "apr_file_io.h" #include #include -#include "boost/lambda/lambda.hpp" -#include "boost/lambda/bind.hpp" +#include +#include +#include #include #include @@ -25,7 +26,7 @@ * Create a text file with specified content "somewhere in the * filesystem," cleaning up when it goes out of scope. */ -class NamedTempFile +class NamedTempFile: public boost::noncopyable { public: NamedTempFile(const std::string& pfx, const std::string& content, apr_pool_t* pool=gAPRPoolp): -- cgit v1.2.3 From d99acd56cdc41d72a073a4419e3e51c356e675bb Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 6 Feb 2012 17:06:55 -0500 Subject: ManageAPR should be noncopyable. Make that explicit. Any RAII class should either be noncopyable or should deal appropriately with a copy operation. ManageAPR is intended only for extremely simple cases, and hence should be noncopyable. --- indra/test/manageapr.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'indra/test') diff --git a/indra/test/manageapr.h b/indra/test/manageapr.h index 0c1ca7b7be..2452fb6ae4 100644 --- a/indra/test/manageapr.h +++ b/indra/test/manageapr.h @@ -13,6 +13,7 @@ #define LL_MANAGEAPR_H #include "llapr.h" +#include /** * Declare a static instance of this class for dead-simple ll_init_apr() at @@ -21,7 +22,7 @@ * instances of other classes that depend on APR already being initialized, * the indeterminate static-constructor-order problem rears its ugly head. */ -class ManageAPR +class ManageAPR: public boost::noncopyable { public: ManageAPR() -- cgit v1.2.3 From 10ab4adc86207f86df30ab23d8858c23e7f550ea Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 15 Feb 2012 13:44:43 -0500 Subject: Fix llprocess_test.cpp's exception catching for Linux. In the course of re-enabling the indra/test tests last year, Log generalized a workaround I'd introduced in llsdmessage_test.cpp. In Linux viewer land, a test program trying to catch an expected exception can't seem to catch it by its specific class (across the libllcommon.so boundary), but must instead catch std::runtime_error and validate the typeid().name() string. Log added a macro for this idiom in llevents_tut.cpp. Generalize that macro further for normal-case processing as well, move it to a header file of its own and use it in all known places -- plus the new exception-catching tests in llprocess_test.cpp. --- indra/test/catch_and_store_what_in.h | 86 ++++++++++++++++++++++++++++++++++++ indra/test/llevents_tut.cpp | 69 +++-------------------------- 2 files changed, 92 insertions(+), 63 deletions(-) create mode 100644 indra/test/catch_and_store_what_in.h (limited to 'indra/test') 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..ca4c74099f 100644 --- a/indra/test/llevents_tut.cpp +++ b/indra/test/llevents_tut.cpp @@ -49,46 +49,12 @@ #include // 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 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 '") + @@ -388,12 +350,7 @@ void events_object::test<7>() // after "Mary" and "checked" -- whoops! make(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: @@ -426,12 +383,7 @@ void events_object::test<7>() make(list_of("shoelaces")), make(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. @@ -459,12 +411,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 +452,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()); } -- cgit v1.2.3 From 5adc39753b9392050d961eb01edb31f8a6fbb05d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 24 Feb 2012 16:02:39 -0500 Subject: Update llevents_tut.cpp to use StringVec, not local StringList. --- indra/test/llevents_tut.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'indra/test') diff --git a/indra/test/llevents_tut.cpp b/indra/test/llevents_tut.cpp index ca4c74099f..a9114075fc 100644 --- a/indra/test/llevents_tut.cpp +++ b/indra/test/llevents_tut.cpp @@ -316,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", @@ -330,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(list_of("spot")("checked")("Mary"))); + ensure_equals(collector.result, make(list_of("spot")("checked")("Mary"))); collector.clear(); button.stopListening("Mary"); button.listen("Mary", @@ -339,7 +338,7 @@ void events_object::test<7>() // now "Mary" must come before "spot" make(list_of("spot"))); button.post(2); - ensure_equals(collector.result, make(list_of("Mary")("spot")("checked"))); + ensure_equals(collector.result, make(list_of("Mary")("spot")("checked"))); collector.clear(); button.stopListening("spot"); std::string threw; @@ -373,7 +372,7 @@ void events_object::test<7>() boost::bind(&Collect::add, boost::ref(collector), "shoelaces", _1), make(list_of("checked"))); button.post(3); - ensure_equals(collector.result, make(list_of("Mary")("checked")("yellow")("shoelaces"))); + ensure_equals(collector.result, make(list_of("Mary")("checked")("yellow")("shoelaces"))); collector.clear(); threw.clear(); try @@ -395,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(list_of("Mary")("checked")("yellow")("shoelaces"))); + ensure_equals(collector.result, make(list_of("Mary")("checked")("yellow")("shoelaces"))); } template<> template<> -- cgit v1.2.3 From c8032de94e7fcad3cc8ce45db2d20cb1664ebea9 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 1 Mar 2012 14:37:41 -0500 Subject: Add NamedExtTempFile to invent arbitrary name with specified ext. This arises, for instance, if you want to be able to create a temporary Python module you can import from test scripts. The Python module file MUST have the .py extension. --- indra/test/namedtempfile.h | 97 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 3 deletions(-) (limited to 'indra/test') diff --git a/indra/test/namedtempfile.h b/indra/test/namedtempfile.h index aa7058b111..6069064627 100644 --- a/indra/test/namedtempfile.h +++ b/indra/test/namedtempfile.h @@ -12,6 +12,7 @@ #if ! defined(LL_NAMEDTEMPFILE_H) #define LL_NAMEDTEMPFILE_H +#include "llerror.h" #include "llapr.h" #include "apr_file_io.h" #include @@ -28,6 +29,7 @@ */ 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) @@ -53,12 +55,12 @@ public: createFile(pfx, func); } - ~NamedTempFile() + virtual ~NamedTempFile() { ll_apr_assert_status(apr_file_remove(mPath.c_str(), mPool)); } - std::string getName() const { return mPath; } + virtual std::string getName() const { return mPath; } void peep() { @@ -70,7 +72,7 @@ public: std::cout << "---\n"; } -private: +protected: void createFile(const std::string& pfx, const Streamer& func) { // Create file in a temporary place. @@ -111,4 +113,93 @@ private: 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) */ -- cgit v1.2.3 From f02ded46fe6f166a07673d97301bf169eb0b9ba8 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 5 Mar 2012 13:07:14 -0500 Subject: Make test.cpp support LOGFAIL env var: only failed tests show log. Set LOGFAIL= one of ALL, DEBUG, INFO, WARN, ERROR, NONE. A passing test will run silently, as now; but a failing test will replay log output at the specified level or higher. While at it, support LOGTEST environment variable, same values. This is like setting --debug (or -d), but allows specifying an arbitrary level -- and, unlike --debug, can be set for a TeamCity build config without modifying any scripts or code. Publish LLError::decodeLevel(std::string), previously private to llerror.cpp. --- indra/test/test.cpp | 126 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 114 insertions(+), 12 deletions(-) (limited to 'indra/test') diff --git a/indra/test/test.cpp b/indra/test/test.cpp index 1adcfb6f45..128d84e428 100644 --- a/indra/test/test.cpp +++ b/indra/test/test.cpp @@ -38,6 +38,7 @@ #include "llerrorcontrol.h" #include "lltut.h" #include "stringize.h" +#include "namedtempfile.h" #include "apr_pools.h" #include "apr_getopt.h" @@ -69,17 +70,79 @@ #include #include +#include + +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: + LLReplayLogReal(LLError::ELevel level, apr_pool_t* pool): + mOldSettings(LLError::saveAndResetSettings()), + mTempFile("log", "", pool), // create file + mFile(mTempFile.getName().c_str()) // open it + { + LLError::setFatalFunction(wouldHaveCrashed); + LLError::setDefaultLevel(level); + LLError::addRecorder(this); + } + + virtual ~LLReplayLogReal() + { + LLError::removeRecorder(this); + 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; + 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 replayer) : mVerboseMode(verbose_mode), mTotalTests(0), mPassedTests(0), @@ -87,7 +150,8 @@ 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::cout, boost::lambda::_1)) + mStream(boost::shared_ptr(&std::cout, boost::lambda::_1)), + mReplayer(replayer) { if (stream) { @@ -125,6 +189,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()) @@ -205,6 +279,7 @@ protected: int mFailedTests; int mSkippedTests; boost::shared_ptr mStream; + boost::shared_ptr mReplayer; }; // TeamCity specific class which emits service messages @@ -213,8 +288,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 replayer) : + LLTestCallback(verbose_mode, stream, replayer) { } @@ -355,6 +431,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; @@ -391,8 +475,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(); @@ -467,8 +557,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': @@ -483,14 +571,28 @@ int main(int argc, char **argv) // run the tests + const char* LOGFAIL = getenv("LOGFAIL"); + boost::shared_ptr 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); + mycallback = new LLTCTestCallback(verbose_mode, output, replayer); } else { - mycallback = new LLTestCallback(verbose_mode, output); + mycallback = new LLTestCallback(verbose_mode, output, replayer); } tut::runner.get().set_callback(mycallback); -- cgit v1.2.3 From eb1bea222322385e6e5b05206f09f21bb891f3f7 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 23 Apr 2012 11:26:18 -0400 Subject: IQA-463: LLError::addRecorder() claims ownership of passed Recorder*. That is, when the underlying LLError::Settings object is destroyed -- possibly at termination, possibly on LLError::restoreSettings() -- the passed Recorder* is deleted. There was much existing code that seemed as unaware of this alarming fact as I was myself. Passing to addRecorder() a pointer to a stack object, or to a member of some other object, is just Bad. It might be preferable to make addRecorder() accept std::auto_ptr to make the ownership transfer more explicit -- or even boost::shared_ptr instead, which would allow the caller to either forget or retain the passed Recorder. This preliminary pass retains the Recorder* dumb pointer API, but documents the ownership issue, and eliminates known instances of passing pointers to anything but a standalone heap Recorder subclass object. --- indra/test/test.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'indra/test') diff --git a/indra/test/test.cpp b/indra/test/test.cpp index 128d84e428..06128e0902 100644 --- a/indra/test/test.cpp +++ b/indra/test/test.cpp @@ -37,6 +37,7 @@ #include "linden_common.h" #include "llerrorcontrol.h" #include "lltut.h" +#include "tests/wrapllerrs.h" // RecorderProxy #include "stringize.h" #include "namedtempfile.h" @@ -91,22 +92,24 @@ public: virtual void replay(std::ostream&) {} }; -class LLReplayLogReal: public LLReplayLog, public LLError::Recorder +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(this); + LLError::addRecorder(mProxy); } virtual ~LLReplayLogReal() { - LLError::removeRecorder(this); + LLError::removeRecorder(mProxy); + delete mProxy; LLError::restoreSettings(mOldSettings); } @@ -134,6 +137,7 @@ public: private: LLError::Settings* mOldSettings; + LLError::Recorder* mProxy; NamedTempFile mTempFile; std::ofstream mFile; }; -- cgit v1.2.3 From 95a147dea7a8c4f2a20a2623fda0f9af5ce973ad Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 9 May 2012 23:03:37 -0400 Subject: CHOP-900: Fix test.cpp merge errors merging up to viewer-release --- indra/test/test.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) (limited to 'indra/test') diff --git a/indra/test/test.cpp b/indra/test/test.cpp index 48f20b2e79..9d24383bcc 100644 --- a/indra/test/test.cpp +++ b/indra/test/test.cpp @@ -66,6 +66,7 @@ #pragma warning (pop) #endif +#include #include #include #include @@ -147,17 +148,16 @@ class LLTestCallback : public tut::callback public: LLTestCallback(bool verbose_mode, std::ostream *stream, boost::shared_ptr 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::cout, boost::lambda::_1)), - mReplayer(replayer) - if (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::cout, boost::lambda::_1)), + mReplayer(replayer) + { if (stream) { // We want a boost::iostreams::tee_device that will stream to two @@ -593,11 +593,11 @@ int main(int argc, char **argv) LLTestCallback* mycallback; if (getenv("TEAMCITY_PROJECT_NAME")) { - mycallback = new LLTCTestCallback(verbose_mode, output, replayer); + mycallback = new LLTCTestCallback(verbose_mode, output.get(), replayer); } else { - mycallback = new LLTestCallback(verbose_mode, output, replayer); + mycallback = new LLTestCallback(verbose_mode, output.get(), replayer); } tut::runner.get().set_callback(mycallback); -- cgit v1.2.3