summaryrefslogtreecommitdiff
path: root/indra/test
diff options
context:
space:
mode:
authorNat Goodspeed <nat@lindenlab.com>2012-02-27 11:51:57 -0500
committerNat Goodspeed <nat@lindenlab.com>2012-02-27 11:51:57 -0500
commite7ab3da7a7f5c68e3544a64c30f011762572995a (patch)
tree65a8aab420f23e7b53c72c7326a26a6c4901489f /indra/test
parentc0318d1bf988217e1fbb0593d03c4f0235a13ea3 (diff)
parent4a7848148e886676dd24bfcf4f50db06bffb28da (diff)
Automated merge with file:///Users/nat/linden/viewer-leap-daggy
Diffstat (limited to 'indra/test')
-rw-r--r--indra/test/catch_and_store_what_in.h86
-rw-r--r--indra/test/llevents_tut.cpp78
-rw-r--r--indra/test/manageapr.h46
-rw-r--r--indra/test/namedtempfile.h114
-rw-r--r--indra/test/test.cpp191
5 files changed, 376 insertions, 139 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..aa7058b111
--- /dev/null
+++ b/indra/test/namedtempfile.h
@@ -0,0 +1,114 @@
+/**
+ * @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 <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
+{
+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);
+ }
+
+ ~NamedTempFile()
+ {
+ ll_apr_assert_status(apr_file_remove(mPath.c_str(), mPool));
+ }
+
+ 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)
+ {
+ // 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;
+};
+
+#endif /* ! defined(LL_NAMEDTEMPFILE_H) */
diff --git a/indra/test/test.cpp b/indra/test/test.cpp
index ffdb0cb976..1adcfb6f45 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 "stringize.h"
#include "apr_pools.h"
#include "apr_getopt.h"
@@ -53,6 +54,21 @@
#include <gtest/gtest.h>
#endif
+#if LL_MSVC
+#pragma warning (push)
+#pragma warning (disable : 4702) // warning C4702: unreachable code
+#endif
+#include <boost/iostreams/tee.hpp>
+#include <boost/iostreams/stream.hpp>
+#if LL_MSVC
+#pragma warning (pop)
+#endif
+
+#include <boost/shared_ptr.hpp>
+#include <boost/make_shared.hpp>
+#include <boost/foreach.hpp>
+#include <boost/lambda/lambda.hpp>
+
namespace tut
{
std::string sSourceDir;
@@ -69,8 +85,24 @@ public:
mPassedTests(0),
mFailedTests(0),
mSkippedTests(0),
- mStream(stream)
+ // 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))
{
+ if (stream)
+ {
+ // We want a boost::iostreams::tee_device that will stream to two
+ // std::ostreams.
+ typedef boost::iostreams::tee_device<std::ostream, std::ostream> TeeDevice;
+ // More than that, though, we want an actual stream using that
+ // device.
+ typedef boost::iostreams::stream<TeeDevice> TeeStream;
+ // Allocate and assign in two separate steps, per Herb Sutter.
+ // (Until we turn on C++11 support, have to wrap *stream with
+ // boost::ref() due to lack of perfect forwarding.)
+ boost::shared_ptr<std::ostream> pstream(new TeeStream(std::cout, boost::ref(*stream)));
+ mStream = pstream;
+ }
}
~LLTestCallback()
@@ -94,7 +126,10 @@ public:
{
++mTotalTests;
std::ostringstream out;
- out << "[" << tr.group << ", " << tr.test << "] ";
+ out << "[" << tr.group << ", " << tr.test;
+ if (! tr.name.empty())
+ out << ": " << tr.name;
+ out << "] ";
switch(tr.result)
{
case tut::test_result::ok:
@@ -123,56 +158,43 @@ public:
break;
default:
++mFailedTests;
- out << "unknown";
+ out << "unknown (tr.result == " << tr.result << ")";
}
if(mVerboseMode || (tr.result != tut::test_result::ok))
{
+ *mStream << out.str();
if(!tr.message.empty())
{
- out << ": '" << tr.message << "'";
+ *mStream << ": '" << tr.message << "'";
}
- if (mStream)
- {
- *mStream << out.str() << std::endl;
- }
-
- std::cout << out.str() << std::endl;
+ *mStream << std::endl;
}
}
- virtual void run_completed()
- {
- if (mStream)
- {
- run_completed_(*mStream);
- }
- run_completed_(std::cout);
- }
-
virtual int getFailedTests() const { return mFailedTests; }
- virtual void run_completed_(std::ostream &stream)
+ virtual void run_completed()
{
- stream << "\tTotal Tests:\t" << mTotalTests << std::endl;
- stream << "\tPassed Tests:\t" << mPassedTests;
+ *mStream << "\tTotal Tests:\t" << mTotalTests << std::endl;
+ *mStream << "\tPassed Tests:\t" << mPassedTests;
if (mPassedTests == mTotalTests)
{
- stream << "\tYAY!! \\o/";
+ *mStream << "\tYAY!! \\o/";
}
- stream << std::endl;
+ *mStream << std::endl;
if (mSkippedTests > 0)
{
- stream << "\tSkipped known failures:\t" << mSkippedTests
+ *mStream << "\tSkipped known failures:\t" << mSkippedTests
<< std::endl;
}
if(mFailedTests > 0)
{
- stream << "*********************************" << std::endl;
- stream << "Failed Tests:\t" << mFailedTests << std::endl;
- stream << "Please report or fix the problem." << std::endl;
- stream << "*********************************" << std::endl;
+ *mStream << "*********************************" << std::endl;
+ *mStream << "Failed Tests:\t" << mFailedTests << std::endl;
+ *mStream << "Please report or fix the problem." << std::endl;
+ *mStream << "*********************************" << std::endl;
}
}
@@ -182,7 +204,7 @@ protected:
int mPassedTests;
int mFailedTests;
int mSkippedTests;
- std::ostream *mStream;
+ boost::shared_ptr<std::ostream> mStream;
};
// TeamCity specific class which emits service messages
@@ -192,84 +214,111 @@ class LLTCTestCallback : public LLTestCallback
{
public:
LLTCTestCallback(bool verbose_mode, std::ostream *stream) :
- LLTestCallback(verbose_mode, stream),
- mTCStream()
+ LLTestCallback(verbose_mode, stream)
{
}
~LLTCTestCallback()
{
- }
+ }
virtual void group_started(const std::string& name) {
LLTestCallback::group_started(name);
- mTCStream << "\n##teamcity[testSuiteStarted name='" << name << "']" << std::endl;
+ std::cout << "\n##teamcity[testSuiteStarted name='" << escape(name) << "']" << std::endl;
}
virtual void group_completed(const std::string& name) {
LLTestCallback::group_completed(name);
- mTCStream << "##teamcity[testSuiteFinished name='" << name << "']" << std::endl;
+ std::cout << "##teamcity[testSuiteFinished name='" << escape(name) << "']" << std::endl;
}
virtual void test_completed(const tut::test_result& tr)
{
+ std::string testname(STRINGIZE(tr.group << "." << tr.test));
+ if (! tr.name.empty())
+ {
+ testname.append(":");
+ testname.append(tr.name);
+ }
+ testname = escape(testname);
+
+ // Sadly, tut::callback doesn't give us control at test start; have to
+ // backfill start message into TC output.
+ std::cout << "##teamcity[testStarted name='" << testname << "']" << std::endl;
+
+ // now forward call to base class so any output produced there is in
+ // the right TC context
LLTestCallback::test_completed(tr);
switch(tr.result)
{
case tut::test_result::ok:
- mTCStream << "##teamcity[testStarted name='" << tr.group << "." << tr.test << "']" << std::endl;
- mTCStream << "##teamcity[testFinished name='" << tr.group << "." << tr.test << "']" << std::endl;
break;
+
case tut::test_result::fail:
- mTCStream << "##teamcity[testStarted name='" << tr.group << "." << tr.test << "']" << std::endl;
- mTCStream << "##teamcity[testFailed name='" << tr.group << "." << tr.test << "' message='" << tr.message << "']" << std::endl;
- mTCStream << "##teamcity[testFinished name='" << tr.group << "." << tr.test << "']" << std::endl;
- break;
case tut::test_result::ex:
- mTCStream << "##teamcity[testStarted name='" << tr.group << "." << tr.test << "']" << std::endl;
- mTCStream << "##teamcity[testFailed name='" << tr.group << "." << tr.test << "' message='" << tr.message << "']" << std::endl;
- mTCStream << "##teamcity[testFinished name='" << tr.group << "." << tr.test << "']" << std::endl;
- break;
case tut::test_result::warn:
- mTCStream << "##teamcity[testStarted name='" << tr.group << "." << tr.test << "']" << std::endl;
- mTCStream << "##teamcity[testFailed name='" << tr.group << "." << tr.test << "' message='" << tr.message << "']" << std::endl;
- mTCStream << "##teamcity[testFinished name='" << tr.group << "." << tr.test << "']" << std::endl;
- break;
case tut::test_result::term:
- mTCStream << "##teamcity[testStarted name='" << tr.group << "." << tr.test << "']" << std::endl;
- mTCStream << "##teamcity[testFailed name='" << tr.group << "." << tr.test << "' message='" << tr.message << "']" << std::endl;
- mTCStream << "##teamcity[testFinished name='" << tr.group << "." << tr.test << "']" << std::endl;
+ std::cout << "##teamcity[testFailed name='" << testname
+ << "' message='" << escape(tr.message) << "']" << std::endl;
break;
+
case tut::test_result::skip:
- mTCStream << "##teamcity[testStarted name='" << tr.group << "." << tr.test << "']" << std::endl;
- mTCStream << "##teamcity[testIgnored name='" << tr.group << "." << tr.test << "']" << std::endl;
- mTCStream << "##teamcity[testFinished name='" << tr.group << "." << tr.test << "']" << std::endl;
+ std::cout << "##teamcity[testIgnored name='" << testname << "']" << std::endl;
break;
+
default:
break;
}
+ std::cout << "##teamcity[testFinished name='" << testname << "']" << std::endl;
}
- virtual void run_completed()
- {
- LLTestCallback::run_completed();
-
- // dump the TC reporting results to cout
- tc_run_completed_(std::cout);
- }
-
- virtual void tc_run_completed_(std::ostream &stream)
+ static std::string escape(const std::string& str)
{
-
- // dump the TC reporting results to cout
- stream << mTCStream.str() << std::endl;
+ // Per http://confluence.jetbrains.net/display/TCD65/Build+Script+Interaction+with+TeamCity#BuildScriptInteractionwithTeamCity-ServiceMessages
+ std::string result;
+ BOOST_FOREACH(char c, str)
+ {
+ switch (c)
+ {
+ case '\'':
+ result.append("|'");
+ break;
+ case '\n':
+ result.append("|n");
+ break;
+ case '\r':
+ result.append("|r");
+ break;
+/*==========================================================================*|
+ // These are not possible 'char' values from a std::string.
+ case '\u0085': // next line
+ result.append("|x");
+ break;
+ case '\u2028': // line separator
+ result.append("|l");
+ break;
+ case '\u2029': // paragraph separator
+ result.append("|p");
+ break;
+|*==========================================================================*/
+ case '|':
+ result.append("||");
+ break;
+ case '[':
+ result.append("|[");
+ break;
+ case ']':
+ result.append("|]");
+ break;
+ default:
+ result.push_back(c);
+ break;
+ }
+ }
+ return result;
}
-
-protected:
- std::ostringstream mTCStream;
-
};