Revamp catch appveyor reporter to report all test results in a batch

* We also only report one test per test-case, as trying to handle sections
  separately is unreliable.
This commit is contained in:
baldurk
2019-01-09 16:27:15 +00:00
parent 22f8b2ac79
commit 7ad3197982
+124 -128
View File
@@ -41,68 +41,8 @@ struct AppVeyorListener : Catch::TestEventListenerBase
{
using TestEventListenerBase::TestEventListenerBase; // inherit constructor
bool enabled = false;
std::string hostname;
uint16_t port = 0;
virtual void testRunStarting(Catch::TestRunInfo const &testRunInfo)
{
const char *url = Process::GetEnvVariable("APPVEYOR_API_URL");
if(url)
{
if(strncmp(url, "http://", 7))
return;
url += 7;
const char *sep = strchr(url, ':');
if(!sep)
return;
hostname = std::string(url, sep);
url = sep + 1;
port = 0;
while(*url >= '0' && *url <= '9')
{
port *= 10;
port += int((*url) - '0');
url++;
}
Network::Socket *sock = Network::CreateClientSocket(hostname.c_str(), port, 10);
if(sock)
enabled = true;
SAFE_DELETE(sock);
}
Catch::TestEventListenerBase::testRunStarting(testRunInfo);
}
virtual void sectionStarting(Catch::SectionInfo const &sectionInfo)
{
Catch::TestEventListenerBase::sectionStarting(sectionInfo);
if(enabled)
{
Network::Socket *sock = Network::CreateClientSocket(hostname.c_str(), port, 10);
if(sock)
{
std::string req = MakeHTTPRequest();
sock->SendDataBlocking(req.c_str(), (uint32_t)req.size());
}
SAFE_DELETE(sock);
}
}
std::string errorList;
double durationInSeconds = 0.0;
virtual bool assertionEnded(Catch::AssertionStats const &assertionStats)
{
@@ -113,6 +53,15 @@ struct AppVeyorListener : Catch::TestEventListenerBase
if(!assertionStats.assertionResult.isOk())
{
std::ostringstream msg;
msg << "-------------------------------------------------------------------------------\n";
for(size_t i = 0; i < m_sectionStack.size(); i++)
{
if(i > 0)
msg << " > ";
msg << m_sectionStack[i].name;
msg << "\n";
}
msg << "-------------------------------------------------------------------------------\n";
msg << assertionStats.assertionResult.getSourceInfo() << ": ";
switch(assertionStats.assertionResult.getResultType())
@@ -149,98 +98,145 @@ struct AppVeyorListener : Catch::TestEventListenerBase
return true;
}
struct TestCase
{
double durationInSeconds;
bool passed;
std::string errorList;
std::string name;
std::string filename;
std::string MakeJSON();
};
std::vector<TestCase> m_testcases;
virtual void sectionEnded(Catch::SectionStats const &sectionStats)
{
if(enabled)
{
Network::Socket *sock = Network::CreateClientSocket(hostname.c_str(), port, 10);
if(sock)
{
std::string req = MakeHTTPRequest(sectionStats.durationInSeconds * 1000.0,
sectionStats.assertions.allOk());
sock->SendDataBlocking(req.c_str(), (uint32_t)req.size());
}
errorList.clear();
SAFE_DELETE(sock);
}
durationInSeconds += sectionStats.durationInSeconds;
Catch::TestEventListenerBase::sectionEnded(sectionStats);
}
private:
std::string MakeHTTPRequest(double msDuration = -1.0, bool passed = false)
virtual void testCaseEnded(Catch::TestCaseStats const &testCaseStats)
{
std::string json;
m_testcases.push_back({
durationInSeconds, testCaseStats.totals.assertions.allOk(), errorList,
testCaseStats.testInfo.name, testCaseStats.testInfo.lineInfo.file,
});
bool update = msDuration >= 0.0;
errorList.clear();
durationInSeconds = 0.0;
const char *outcome = "Running";
Catch::TestEventListenerBase::testCaseEnded(testCaseStats);
}
if(update)
outcome = passed ? "Passed" : "Failed";
// we dump all the test data at the end, because appveyor can't be trusted to get it right
// incrementally. This means if the program crashes mid-run we don't get partial test output, but
// it should at least by identified as an issue.
virtual void testRunEnded(Catch::TestRunStats const &testRunStats) override
{
const char *url = Process::GetEnvVariable("APPVEYOR_API_URL");
std::string fileName;
std::string testName;
for(const Catch::SectionInfo &section : m_sectionStack)
if(url)
{
if(!testName.empty())
testName += " > ";
testName += section.name;
if(strncmp(url, "http://", 7))
return;
url += 7;
const char *sep = strchr(url, ':');
if(!sep)
return;
std::string hostname = std::string(url, sep);
url = sep + 1;
uint16_t port = 0;
while(*url >= '0' && *url <= '9')
{
port *= 10;
port += int((*url) - '0');
url++;
}
Network::Socket *sock = Network::CreateClientSocket(hostname.c_str(), port, 10);
if(sock)
{
std::string json;
json += "[\n";
for(size_t i = 0; i < m_testcases.size(); i++)
{
json += m_testcases[i].MakeJSON();
if(i + 1 < m_testcases.size())
json += ",";
json += "\n";
}
json += "]";
std::string http;
http += StringFormat::Fmt("POST /api/tests/batch HTTP/1.1\r\n");
http += StringFormat::Fmt("Host: %s\r\n", hostname.c_str());
http += "Connection: close\r\n";
http += "Content-Type: application/json\r\n";
http += StringFormat::Fmt("Content-Length: %zu\r\n", json.size());
http += "User-Agent: Catch.hpp appveyor updater\r\n";
http += "\r\n";
http += json;
sock->SendDataBlocking(http.c_str(), (uint32_t)http.size());
}
SAFE_DELETE(sock);
}
}
};
if(m_sectionStack.empty())
fileName = currentTestCaseInfo->name;
else
fileName = m_sectionStack[0].lineInfo.file;
json = StringFormat::Fmt(R"(
static std::string escape(const std::string &input)
{
std::string ret = input;
size_t i = ret.find_first_of("\"\n\\", 0);
while(i != std::string::npos)
{
if(ret[i] == '"')
ret.replace(i, 1, "\\\"");
else if(ret[i] == '\\')
ret.replace(i, 1, "\\\\");
else if(ret[i] == '\n')
ret.replace(i, 1, "\\n");
i = ret.find_first_of("\"\n\\", i + 2);
}
return ret;
}
std::string AppVeyorListener::TestCase::MakeJSON()
{
std::string json;
return StringFormat::Fmt(
R"({
"testName": "%s",
"testFramework": "Catch.hpp",
"fileName": "%s",
"outcome": "%s",
"durationMilliseconds": "%.0f",
"durationMilliseconds": "%d",
"ErrorMessage": "%s",
"ErrorStackTrace": "",
"StdOut": "",
"StdErr": ""
})",
testName.c_str(), fileName.c_str(), outcome, RDCMAX(msDuration, 0.0),
escape(trim(errorList)).c_str());
escape(name).c_str(), escape(filename).c_str(), passed ? "Passed" : "Failed",
(int)RDCMAX(durationInSeconds * 1000.0, 0.0), escape(trim(errorList)).c_str());
}
std::string http;
http += StringFormat::Fmt("%s /api/tests HTTP/1.1\r\n", update ? "PUT" : "POST");
http += StringFormat::Fmt("Host: %s\r\n", hostname.c_str());
http += "Connection: close\r\n";
http += "Content-Type: application/json\r\n";
http += StringFormat::Fmt("Content-Length: %zu\r\n", json.size());
http += "User-Agent: Catch.hpp appveyor updater\r\n";
http += "\r\n";
return http + json;
}
std::string escape(const std::string &input)
{
std::string ret = input;
size_t i = ret.find_first_of("\"\n\\", 0);
while(i != std::string::npos)
{
if(ret[i] == '"')
ret.replace(i, 1, "\\\"");
else if(ret[i] == '\\')
ret.replace(i, 1, "\\\\");
else if(ret[i] == '\n')
ret.replace(i, 1, "\\n");
i = ret.find_first_of("\"\n\\", i + 2);
}
return ret;
}
};
CATCH_REGISTER_LISTENER(AppVeyorListener)
class LogOutputter : public std::stringbuf