Skip to content
Snippets Groups Projects
Commit 90ba675d authored by Nat Goodspeed's avatar Nat Goodspeed
Browse files

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.
parent b022ebf1
No related branches found
No related tags found
No related merge requests found
......@@ -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 @@ class LLTestCallback : public tut::callback
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 @@ class LLTestCallback : public tut::callback
{
++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 @@ class LLTestCallback : public tut::callback
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 @@ class LLTestCallback : public tut::callback
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;
};
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment