diff --git a/BUILD.gn b/BUILD.gn index a9e796168..96632285d 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -1654,6 +1654,29 @@ if (is_mac) { # 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) { # Helper for generating the CEF app bundle. template("cef_app") { @@ -2001,6 +2024,7 @@ if (is_mac) { ":ceftests_resources_bundle_data", ":ceftests_resources_bundle_data_english", ":ceftests_xibs", + ":gtest_teamcity", "//testing/gtest", ] frameworks = [ @@ -2219,6 +2243,7 @@ if (is_mac) { deps = [ ":libcef", ":libcef_dll_wrapper", + ":gtest_teamcity", "//testing/gtest", ] diff --git a/tests/ceftests/test_suite.cc b/tests/ceftests/test_suite.cc index 30ffc4ef1..47d796e2c 100644 --- a/tests/ceftests/test_suite.cc +++ b/tests/ceftests/test_suite.cc @@ -7,6 +7,7 @@ #include "include/cef_file_util.h" #include "include/wrapper/cef_scoped_temp_dir.h" #include "tests/gtest/include/gtest/gtest.h" +#include "tests/gtest/teamcity/include/teamcity_gtest.h" #include "tests/shared/common/client_switches.h" namespace { @@ -121,6 +122,12 @@ void CefTestSuite::InitMainProcess() { // This will modify |argc_| and |argv_|. 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(). diff --git a/tests/gtest/CMakeLists.txt.in b/tests/gtest/CMakeLists.txt.in index 32fcc8757..84fbf4d5f 100644 --- a/tests/gtest/CMakeLists.txt.in +++ b/tests/gtest/CMakeLists.txt.in @@ -6,6 +6,10 @@ set(CEF_TARGET "cef_gtest") set(GTEST_SRCS 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}) diff --git a/tests/gtest/teamcity/README.cef b/tests/gtest/teamcity/README.cef new file mode 100644 index 000000000..d86ec37ec --- /dev/null +++ b/tests/gtest/teamcity/README.cef @@ -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. diff --git a/tests/gtest/teamcity/include/teamcity_gtest.h b/tests/gtest/teamcity/include/teamcity_gtest.h new file mode 100644 index 000000000..acb1cd259 --- /dev/null +++ b/tests/gtest/teamcity/include/teamcity_gtest.h @@ -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 + +#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 */ diff --git a/tests/gtest/teamcity/src/teamcity_gtest.cpp b/tests/gtest/teamcity/src/teamcity_gtest.cpp new file mode 100644 index 000000000..8600c2c12 --- /dev/null +++ b/tests/gtest/teamcity/src/teamcity_gtest.cpp @@ -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(result->elapsed_time()), flowid); +} + +// Fired after the test case ends. +void TeamcityGoogleTestEventListener::OnTestCaseEnd(const TestCase& test_case) { + messages.suiteFinished(test_case.name(), flowid); +} + +} +} diff --git a/tests/gtest/teamcity/src/teamcity_messages.cpp b/tests/gtest/teamcity/src/teamcity_messages.cpp new file mode 100644 index 000000000..39f868a02 --- /dev/null +++ b/tests/gtest/teamcity/src/teamcity_messages.cpp @@ -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 +#include + +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(); +} + +} +} diff --git a/tests/gtest/teamcity/src/teamcity_messages.h b/tests/gtest/teamcity/src/teamcity_messages.h new file mode 100644 index 000000000..8f64565dd --- /dev/null +++ b/tests/gtest/teamcity/src/teamcity_messages.h @@ -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 +#include + +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 */ diff --git a/tools/make_distrib.py b/tools/make_distrib.py index 8f9946cb8..838bc1834 100644 --- a/tools/make_distrib.py +++ b/tools/make_distrib.py @@ -213,6 +213,11 @@ def create_fuzed_gtest(tests_dir): os.path.join(cef_dir, 'tests', 'gtest', 'README.cef.in'), 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): """ Transfer files from one location to another. """