diff options
author | Nat Goodspeed <nat@lindenlab.com> | 2012-02-03 13:09:20 -0500 |
---|---|---|
committer | Nat Goodspeed <nat@lindenlab.com> | 2012-02-03 13:09:20 -0500 |
commit | 90ba675da4416a7f75f59340633e2c007b6cc029 (patch) | |
tree | fad9b18a330baaff9fc30ba458e94f68759a8648 | |
parent | b022ebf13c9a227f87a112419e237894a1231c8c (diff) |
Escape all strings embedded in TeamCity service messages.
TeamCity requires that certain characters (notably "'") must be escaped when
embedded in service messages:
http://confluence.jetbrains.net/display/TCD65/Build+Script+Interaction+with+TeamCity#BuildScriptInteractionwithTeamCity-ServiceMessages
TUT frequently outputs messages containing "'", e.g. from ensure_equals()
failure. We've seen TC output nesting get confused when it fails to process
service messages properly due to parsing unescaped messages.
Along with test<n> number, report test name (from set_test_name()) when
available.
Eliminate horsing around to produce normal output on both std::cout and
possible output file. When output file is specified, use
boost::iostreams::tee_device to do fanout for us.
Improve placement (and possibly reliability) of service messages.
Clean up a startling amount of redundancy in service-message production.
-rw-r--r-- | indra/test/test.cpp | 183 |
1 files changed, 112 insertions, 71 deletions
diff --git a/indra/test/test.cpp b/indra/test/test.cpp index ffdb0cb976..3e7be29b39 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,13 @@ #include <gtest/gtest.h> #endif +#include <boost/iostreams/tee.hpp> +#include <boost/iostreams/stream.hpp> +#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 +77,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 +118,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 +150,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; - } - } - - virtual void run_completed() - { - if (mStream) - { - run_completed_(*mStream); + *mStream << std::endl; } - 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 +196,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 +206,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; - }; |