/* * The MIT License * * Copyright (c) 2011 Bruno P. Kinoshita * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * * @author Bruno P. Kinoshita * @since 0.1 */ #ifndef TAP_H_ #define TAP_H_ #include #include #include #include #include #include namespace tap { #ifdef GTEST_TAP_13_DIAGNOSTIC // based on http://stackoverflow.com/a/7724536/831180 static std::string replace_all_copy( std::string const& original, std::string const& before, std::string const& after ) { using namespace std; if (before == after) return string(original); string retval; if (before.length() == after.length()) retval.reserve(original.size()); basic_string ::const_iterator end = original.end(); basic_string ::const_iterator current = original.begin(); basic_string ::const_iterator next = search(current, end, before.begin(), before.end()); while ( next != end ) { retval.append( current, next ); retval.append( after ); current = next + before.size(); next = search(current, end, before.begin(), before.end()); } retval.append( current, next ); return retval; } #endif class TestResult { private: int number; std::string status; std::string name; std::string comment; bool skip; public: std::string getComment() const { std::stringstream ss; if (this->skip) { ss << "# SKIP " << this->comment; } else if (!this->comment.empty()) { ss << "# " << this->comment; } return ss.str(); } const std::string& getName() const { return name; } int getNumber() const { return number; } const std::string& getStatus() const { return status; } bool getSkip() const { return skip; } void setComment(const std::string& comment) { this->comment = comment; } void setName(const std::string& name) { this->name = name; } void setNumber(int number) { this->number = number; } void setStatus(const std::string& status) { this->status = status; } void setSkip(bool skip) { this->skip = skip; } std::string toString() const { std::stringstream ss; ss << this->status << " " << this->number << " " << this->name; #ifdef GTEST_TAP_13_DIAGNOSTIC std::string comment_text = this->getComment(); if (!comment_text.empty()) { ss << std::endl << "# Diagnostic" << std::endl << " ---" << std::endl << " " << replace_all_copy(this->getComment(), "\n", "\n "); } #endif return ss.str(); } }; class TestSet { private: std::list testResults; public: const std::list& getTestResults() const { return testResults; } void addTestResult(TestResult& testResult) { testResult.setNumber((this->getNumberOfTests() + 1)); this->testResults.push_back(testResult); } int getNumberOfTests() const { return this->testResults.size(); } std::string toString() const { std::stringstream ss; ss << "1.." << this->getNumberOfTests() << std::endl; for (std::list::const_iterator ci = this->testResults.begin(); ci != this->testResults.end(); ++ci) { TestResult testResult = *ci; ss << testResult.toString() << std::endl; } return ss.str(); } }; class TapListener: public ::testing::EmptyTestEventListener { private: std::map testCaseTestResultMap; void addTapTestResult(const testing::TestInfo& testInfo) { tap::TestResult tapResult; tapResult.setName(testInfo.name()); tapResult.setSkip(!testInfo.should_run()); const testing::TestResult *testResult = testInfo.result(); int number = testResult->total_part_count(); tapResult.setNumber(number-1); if (testResult->HasFatalFailure()) { tapResult.setStatus("Bail out!"); } else if (testResult->Failed()) { tapResult.setStatus("not ok"); tapResult.setComment(testResult->GetTestPartResult(number-1).summary()); } else { tapResult.setStatus("ok"); } this->addNewOrUpdate(testInfo.test_case_name(), tapResult); } std::string getCommentOrDirective(const std::string& comment, bool skip) { std::stringstream commentText; if (skip) { commentText << " # SKIP " << comment; } else if (!comment.empty()) { commentText << " # " << comment; } return commentText.str(); } void addNewOrUpdate(const std::string& testCaseName, tap::TestResult testResult) { std::map::const_iterator ci = this->testCaseTestResultMap.find(testCaseName); if (ci != this->testCaseTestResultMap.end()) { tap::TestSet testSet = ci->second; testSet.addTestResult(testResult); this->testCaseTestResultMap[testCaseName] = testSet; } else { tap::TestSet testSet; testSet.addTestResult(testResult); this->testCaseTestResultMap[testCaseName] = testSet; } } public: virtual void OnTestEnd(const testing::TestInfo& testInfo) { //printf("%s %d - %s\n", testInfo.result()->Passed() ? "ok" : "not ok", this->testNumber, testInfo.name()); this->addTapTestResult(testInfo); } virtual void OnTestProgramEnd(const testing::UnitTest& unit_test) { //--- Write the count and the word. std::map::const_iterator ci; for (ci = this->testCaseTestResultMap.begin(); ci != this->testCaseTestResultMap.end(); ++ci) { const tap::TestSet& testSet = ci->second; #ifdef GTEST_TAP_PRINT_TO_STDOUT std::cout << "TAP version 13" << std::endl; std::cout << testSet.toString(); #else std::string ext = ".tap"; std::ofstream tapFile; tapFile.open((ci->first + ext).c_str()); tapFile << testSet.toString(); tapFile.close(); #endif } } }; } // namespace tap #endif // TAP_H_