gtest: Add plugin for TeamCity integration

Automatically report unit tests to the TeamCity server if run within TeamCity.
See tests/gtest/teamcity/README.cef for details.

To test: Set the TEAMCITY_PROJECT_NAME environment variable and run ceftests.
This commit is contained in:
Marshall Greenblatt 2021-06-24 16:52:37 -04:00
parent 50f627b07e
commit 7efac13ac1
9 changed files with 445 additions and 0 deletions

View File

@ -1654,6 +1654,29 @@ if (is_mac) {
# Executable/app targets. # Executable/app targets.
# #
# Source files for TeamCity GTest integration.
# See tests/gtest/teamcity/README.cef for details.
source_set("gtest_teamcity") {
testonly = true
sources = [
"tests/gtest/teamcity/include/teamcity_gtest.h",
"tests/gtest/teamcity/src/teamcity_gtest.cpp",
"tests/gtest/teamcity/src/teamcity_gtest.h",
"tests/gtest/teamcity/src/teamcity_messages.cpp",
"tests/gtest/teamcity/src/teamcity_messages.h",
]
deps = [
"//testing/gtest",
]
configs += [
"libcef/features:config",
"//build/config:precompiled_headers",
]
}
if (is_mac) { if (is_mac) {
# Helper for generating the CEF app bundle. # Helper for generating the CEF app bundle.
template("cef_app") { template("cef_app") {
@ -2001,6 +2024,7 @@ if (is_mac) {
":ceftests_resources_bundle_data", ":ceftests_resources_bundle_data",
":ceftests_resources_bundle_data_english", ":ceftests_resources_bundle_data_english",
":ceftests_xibs", ":ceftests_xibs",
":gtest_teamcity",
"//testing/gtest", "//testing/gtest",
] ]
frameworks = [ frameworks = [
@ -2219,6 +2243,7 @@ if (is_mac) {
deps = [ deps = [
":libcef", ":libcef",
":libcef_dll_wrapper", ":libcef_dll_wrapper",
":gtest_teamcity",
"//testing/gtest", "//testing/gtest",
] ]

View File

@ -7,6 +7,7 @@
#include "include/cef_file_util.h" #include "include/cef_file_util.h"
#include "include/wrapper/cef_scoped_temp_dir.h" #include "include/wrapper/cef_scoped_temp_dir.h"
#include "tests/gtest/include/gtest/gtest.h" #include "tests/gtest/include/gtest/gtest.h"
#include "tests/gtest/teamcity/include/teamcity_gtest.h"
#include "tests/shared/common/client_switches.h" #include "tests/shared/common/client_switches.h"
namespace { namespace {
@ -121,6 +122,12 @@ void CefTestSuite::InitMainProcess() {
// This will modify |argc_| and |argv_|. // This will modify |argc_| and |argv_|.
testing::InitGoogleTest(&argc_, argv_.array()); testing::InitGoogleTest(&argc_, argv_.array());
if (jetbrains::teamcity::underTeamcity()) {
auto& listeners = ::testing::UnitTest::GetInstance()->listeners();
listeners.Append(
new jetbrains::teamcity::TeamcityGoogleTestEventListener());
}
} }
// Don't add additional code to this method. Instead add it to Initialize(). // Don't add additional code to this method. Instead add it to Initialize().

View File

@ -6,6 +6,10 @@ set(CEF_TARGET "cef_gtest")
set(GTEST_SRCS set(GTEST_SRCS
src/gtest-all.cc src/gtest-all.cc
teamcity/include/teamcity_gtest.h
teamcity/src/teamcity_gtest.cpp
teamcity/src/teamcity_messages.cpp
teamcity/src/teamcity_messages.h
) )
source_group(cef_gtest FILES ${GTEST_SRCS}) source_group(cef_gtest FILES ${GTEST_SRCS})

View File

@ -0,0 +1,14 @@
Name: TeamCity C++ Unit Test Reporting
Short Name: teamcity-cpp
URL: https://github.com/JetBrains/teamcity-cpp
Version: 1.8 (commit b2c95c5)
License: Apache 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
Description:
GTest integration for reporting to TeamCity Continuous Integration Server.
See the project URL for related documentation.
Local Modifications:
1. Copy necessary C++ files only.
1. Use absolute include paths.

View File

@ -0,0 +1,54 @@
/* Copyright 2015 Paul Shmakov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef H_TEAMCITY_GTEST
#define H_TEAMCITY_GTEST
#include <string>
#include "tests/gtest/include/gtest/gtest.h"
#include "tests/gtest/teamcity/src/teamcity_messages.h"
namespace jetbrains {
namespace teamcity {
class TeamcityGoogleTestEventListener: public ::testing::EmptyTestEventListener {
public:
TeamcityGoogleTestEventListener(const std::string& flowid);
TeamcityGoogleTestEventListener();
// Fired before the test case starts.
virtual void OnTestCaseStart(const ::testing::TestCase& test_case);
// Fired before the test starts.
virtual void OnTestStart(const ::testing::TestInfo& test_info);
// Fired after the test ends.
virtual void OnTestEnd(const ::testing::TestInfo& test_info);
// Fired after the test case ends.
virtual void OnTestCaseEnd(const ::testing::TestCase& test_case);
private:
TeamcityMessages messages;
std::string flowid;
// Prevent copying.
TeamcityGoogleTestEventListener(const TeamcityGoogleTestEventListener&);
void operator =(const TeamcityGoogleTestEventListener&);
};
}
}
#endif /* H_TEAMCITY_GTEST */

View File

@ -0,0 +1,86 @@
/* Copyright 2015 Paul Shmakov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include "tests/gtest/teamcity/include/teamcity_gtest.h"
namespace jetbrains {
namespace teamcity {
using namespace testing;
TeamcityGoogleTestEventListener::TeamcityGoogleTestEventListener() {
flowid = getFlowIdFromEnvironment();
}
TeamcityGoogleTestEventListener::TeamcityGoogleTestEventListener(const std::string& flowid_)
: flowid(flowid_) {
}
// Fired before the test case starts.
void TeamcityGoogleTestEventListener::OnTestCaseStart(const TestCase& test_case) {
messages.suiteStarted(test_case.name(), flowid);
}
// Fired before the test starts.
void TeamcityGoogleTestEventListener::OnTestStart(const TestInfo& test_info) {
messages.testStarted(test_info.name(), flowid);
}
// Fired after the test ends.
void TeamcityGoogleTestEventListener::OnTestEnd(const TestInfo& test_info) {
const TestResult* result = test_info.result();
if (result->Failed()) {
std::string message;
std::string details;
for (int i = 0; i < result->total_part_count(); ++i) {
const TestPartResult& partResult = result->GetTestPartResult(i);
if (partResult.passed()) {
continue;
}
if (message.empty()) {
message = partResult.summary();
}
if (!details.empty()) {
details.append("\n");
}
details.append(partResult.message());
if (partResult.file_name() && partResult.line_number() >= 0) {
std::stringstream ss;
ss << "\n at " << partResult.file_name() << ":" << partResult.line_number();
details.append(ss.str());
}
}
messages.testFailed(
test_info.name(),
!message.empty() ? message : "failed",
details,
flowid
);
}
messages.testFinished(test_info.name(), static_cast<int>(result->elapsed_time()), flowid);
}
// Fired after the test case ends.
void TeamcityGoogleTestEventListener::OnTestCaseEnd(const TestCase& test_case) {
messages.suiteFinished(test_case.name(), flowid);
}
}
}

View File

@ -0,0 +1,189 @@
/* Copyright 2011 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* $Revision: 88625 $
*/
#include "tests/gtest/teamcity/src/teamcity_messages.h"
#include <cstdlib>
#include <sstream>
namespace jetbrains {
namespace teamcity {
std::string getFlowIdFromEnvironment() {
#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32)) && !defined(__CYGWIN__) && !defined(__MINGW32__)
char *flowId = NULL;
size_t sz = 0;
std::string result;
if(!_dupenv_s(&flowId, &sz,"TEAMCITY_PROCESS_FLOW_ID")) {
result = flowId != NULL ? flowId : "";
free(flowId);
}
return result;
#else
const char *flowId = getenv("TEAMCITY_PROCESS_FLOW_ID");
return flowId == NULL ? "" : flowId;
#endif
}
bool underTeamcity() {
#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32)) && !defined(__CYGWIN__) && !defined(__MINGW32__)
char *teamCityProjectName = 0;
size_t sz = 0;
bool result = false;
if(!_dupenv_s(&teamCityProjectName, &sz, "TEAMCITY_PROJECT_NAME")) {
result = teamCityProjectName != NULL;
free(teamCityProjectName);
}
return result;
#else
return getenv("TEAMCITY_PROJECT_NAME") != NULL;
#endif
}
TeamcityMessages::TeamcityMessages()
: m_out(&std::cout)
{}
void TeamcityMessages::setOutput(std::ostream &out) {
m_out = &out;
}
std::string TeamcityMessages::escape(const std::string &s) {
std::string result;
result.reserve(s.length());
for (size_t i = 0; i < s.length(); i++) {
char c = s[i];
switch (c) {
case '\n': result.append("|n"); break;
case '\r': result.append("|r"); break;
case '\'': result.append("|'"); break;
case '|': result.append("||"); break;
case ']': result.append("|]"); break;
default: result.append(&c, 1);
}
}
return result;
}
void TeamcityMessages::openMsg(const std::string &name) {
// endl for http://jetbrains.net/tracker/issue/TW-4412
*m_out << std::endl << "##teamcity[" << name;
}
void TeamcityMessages::closeMsg() {
*m_out << "]";
// endl for http://jetbrains.net/tracker/issue/TW-4412
*m_out << std::endl;
}
void TeamcityMessages::writeProperty(const std::string &name, const std::string &value) {
*m_out << " " << name << "='" << escape(value) << "'";
}
void TeamcityMessages::suiteStarted(const std::string &name, const std::string &flowid) {
openMsg("testSuiteStarted");
writeProperty("name", name);
if(flowid.length() > 0) {
writeProperty("flowId", flowid);
}
closeMsg();
}
void TeamcityMessages::suiteFinished(const std::string &name, const std::string &flowid) {
openMsg("testSuiteFinished");
writeProperty("name", name);
if(flowid.length() > 0) {
writeProperty("flowId", flowid);
}
closeMsg();
}
void TeamcityMessages::testStarted(const std::string &name, const std::string &flowid, bool captureStandardOutput) {
openMsg("testStarted");
writeProperty("name", name);
if(flowid.length() > 0) {
writeProperty("flowId", flowid);
}
if(captureStandardOutput) {
writeProperty("captureStandardOutput", "true"); // false by default
}
closeMsg();
}
void TeamcityMessages::testFinished(const std::string &name, int durationMs, const std::string &flowid) {
openMsg("testFinished");
writeProperty("name", name);
if(flowid.length() > 0) {
writeProperty("flowId", flowid);
}
if(durationMs >= 0) {
std::stringstream out(std::ios_base::out);
out << durationMs;
writeProperty("duration", out.str());
}
closeMsg();
}
void TeamcityMessages::testFailed(const std::string &name, const std::string &message, const std::string &details, const std::string &flowid) {
openMsg("testFailed");
writeProperty("name", name);
writeProperty("message", message);
writeProperty("details", details);
if(flowid.length() > 0) {
writeProperty("flowId", flowid);
}
closeMsg();
}
void TeamcityMessages::testIgnored(const std::string &name, const std::string &message, const std::string &flowid) {
openMsg("testIgnored");
writeProperty("name", name);
writeProperty("message", message);
if(flowid.length() > 0) {
writeProperty("flowId", flowid);
}
closeMsg();
}
void TeamcityMessages::testOutput(const std::string &name, const std::string &output, const std::string &flowid, bool isStdError) {
openMsg(isStdError ? "testStdErr" : "testStdOut");
writeProperty("name", name);
writeProperty("out", output);
if(flowid.length() > 0) {
writeProperty("flowId", flowid);
}
closeMsg();
}
}
}

View File

@ -0,0 +1,61 @@
/* Copyright 2011 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* $Revision: 88625 $
*/
#ifndef H_TEAMCITY_MESSAGES
#define H_TEAMCITY_MESSAGES
#include <iostream>
#include <string>
namespace jetbrains {
namespace teamcity {
std::string getFlowIdFromEnvironment();
bool underTeamcity();
class TeamcityMessages {
std::ostream *m_out;
protected:
std::string escape(const std::string &s);
void openMsg(const std::string &name);
void writeProperty(const std::string &name, const std::string &value);
void closeMsg();
public:
static const bool StdErr = true;
static const bool StdOut = false;
TeamcityMessages();
void setOutput(std::ostream &);
void suiteStarted(const std::string &name, const std::string &flowid = std::string());
void suiteFinished(const std::string &name, const std::string &flowid = std::string());
void testStarted(const std::string &name, const std::string &flowid = std::string(), bool captureStandardOutput = false);
void testFailed(const std::string &name, const std::string &message, const std::string &details, const std::string &flowid = std::string());
void testIgnored(const std::string &name, const std::string &message, const std::string &flowid = std::string());
void testOutput(const std::string &name, const std::string &output, const std::string &flowid, bool isStdErr = StdOut);
void testFinished(const std::string &name, int durationMs = -1, const std::string &flowid = std::string());
};
}
}
#endif /* H_TEAMCITY_MESSAGES */

View File

@ -213,6 +213,11 @@ def create_fuzed_gtest(tests_dir):
os.path.join(cef_dir, 'tests', 'gtest', 'README.cef.in'), os.path.join(cef_dir, 'tests', 'gtest', 'README.cef.in'),
os.path.join(target_gtest_dir, 'README.cef'), options.quiet) os.path.join(target_gtest_dir, 'README.cef'), options.quiet)
# Copy tests/gtest/teamcity files
copy_dir(
os.path.join(cef_dir, 'tests', 'gtest', 'teamcity'),
os.path.join(target_gtest_dir, 'teamcity'), options.quiet)
def transfer_gypi_files(src_dir, gypi_paths, gypi_path_prefix, dst_dir, quiet): def transfer_gypi_files(src_dir, gypi_paths, gypi_path_prefix, dst_dir, quiet):
""" Transfer files from one location to another. """ """ Transfer files from one location to another. """