diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 8412e3e86c..50e9d2b996 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -439,6 +439,7 @@ set(CTEST_SRCS cmCTest.cxx CTest/cmParseCacheCoverage.cxx CTest/cmParseGTMCoverage.cxx CTest/cmParsePHPCoverage.cxx + CTest/cmParsePythonCoverage.cxx CTest/cmCTestEmptyBinaryDirectoryCommand.cxx CTest/cmCTestGenericHandler.cxx CTest/cmCTestHandlerCommand.cxx diff --git a/Source/CTest/cmCTestCoverageHandler.cxx b/Source/CTest/cmCTestCoverageHandler.cxx index 20aded2b54..ef071b9b80 100644 --- a/Source/CTest/cmCTestCoverageHandler.cxx +++ b/Source/CTest/cmCTestCoverageHandler.cxx @@ -11,6 +11,7 @@ ============================================================================*/ #include "cmCTestCoverageHandler.h" #include "cmParsePHPCoverage.h" +#include "cmParsePythonCoverage.h" #include "cmParseGTMCoverage.h" #include "cmParseCacheCoverage.h" #include "cmCTest.h" @@ -392,6 +393,13 @@ int cmCTestCoverageHandler::ProcessHandler() { return error; } + file_count += this->HandlePythonCoverage(&cont); + error = cont.Error; + if ( file_count < 0 ) + { + return error; + } + file_count += this->HandleMumpsCoverage(&cont); error = cont.Error; if ( file_count < 0 ) @@ -761,6 +769,32 @@ int cmCTestCoverageHandler::HandlePHPCoverage( } return static_cast(cont->TotalCoverage.size()); } + +//---------------------------------------------------------------------- +int cmCTestCoverageHandler::HandlePythonCoverage( + cmCTestCoverageHandlerContainer* cont) +{ + cmParsePythonCoverage cov(*cont, this->CTest); + + // Assume the coverage.xml is in the source directory + std::string coverageXMLFile = this->CTest->GetBinaryDir() + "/coverage.xml"; + + if(cmSystemTools::FileExists(coverageXMLFile.c_str())) + { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Parsing coverage.py XML file: " << coverageXMLFile + << std::endl); + cov.ReadCoverageXML(coverageXMLFile.c_str()); + } + else + { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, + "Cannot find coverage.py XML file: " << coverageXMLFile + << std::endl); + } + return static_cast(cont->TotalCoverage.size()); +} + //---------------------------------------------------------------------- int cmCTestCoverageHandler::HandleMumpsCoverage( cmCTestCoverageHandlerContainer* cont) diff --git a/Source/CTest/cmCTestCoverageHandler.h b/Source/CTest/cmCTestCoverageHandler.h index 92b0b2285a..3506928a57 100644 --- a/Source/CTest/cmCTestCoverageHandler.h +++ b/Source/CTest/cmCTestCoverageHandler.h @@ -70,6 +70,10 @@ private: //! Handle coverage using xdebug php coverage int HandlePHPCoverage(cmCTestCoverageHandlerContainer* cont); + + //! Handle coverage for Python with coverage.py + int HandlePythonCoverage(cmCTestCoverageHandlerContainer* cont); + //! Handle coverage for mumps int HandleMumpsCoverage(cmCTestCoverageHandlerContainer* cont); diff --git a/Source/CTest/cmParsePythonCoverage.cxx b/Source/CTest/cmParsePythonCoverage.cxx new file mode 100644 index 0000000000..a086f139bc --- /dev/null +++ b/Source/CTest/cmParsePythonCoverage.cxx @@ -0,0 +1,113 @@ +#include "cmStandardIncludes.h" +#include "cmSystemTools.h" +#include "cmXMLParser.h" +#include "cmParsePythonCoverage.h" +#include + + +//---------------------------------------------------------------------------- +class cmParsePythonCoverage::XMLParser: public cmXMLParser +{ +public: + XMLParser(cmCTest* ctest, cmCTestCoverageHandlerContainer& cont) + : CTest(ctest), Coverage(cont) + { + } + + virtual ~XMLParser() + { + } + +protected: + + virtual void StartElement(const char* name, const char** atts) + { + if(strcmp(name, "class") == 0) + { + int tagCount = 0; + while(true) + { + if(strcmp(atts[tagCount], "filename") == 0) + { + cmCTestLog(this->CTest, HANDLER_VERBOSE_OUTPUT, "Reading file: " + << atts[tagCount+1] << std::endl); + this->CurFileName = this->Coverage.SourceDir + "/" + + atts[tagCount+1]; + FileLinesType& curFileLines = + this->Coverage.TotalCoverage[this->CurFileName]; + std::ifstream fin(this->CurFileName.c_str()); + if(!fin) + { + cmCTestLog(this->CTest, ERROR_MESSAGE, + "Python Coverage: Error opening " << this->CurFileName + << std::endl); + this->Coverage.Error++; + break; + } + + std::string line; + curFileLines.push_back(-1); + while(cmSystemTools::GetLineFromStream(fin, line)) + { + curFileLines.push_back(-1); + } + + break; + } + ++tagCount; + } + } + else if(strcmp(name, "line") == 0) + { + int tagCount = 0; + int curNumber = -1; + int curHits = -1; + while(true) + { + if(strcmp(atts[tagCount], "hits") == 0) + { + curHits = atoi(atts[tagCount+1]); + } + else if(strcmp(atts[tagCount], "number") == 0) + { + curNumber = atoi(atts[tagCount+1]); + } + + if(curHits > -1 && curNumber > -1) + { + FileLinesType& curFileLines = + this->Coverage.TotalCoverage[this->CurFileName]; + curFileLines[curNumber] = curHits; + break; + } + ++tagCount; + } + } + } + + virtual void EndElement(const char*) {} + +private: + + typedef cmCTestCoverageHandlerContainer::SingleFileCoverageVector + FileLinesType; + cmCTest* CTest; + cmCTestCoverageHandlerContainer& Coverage; + std::string CurFileName; + +}; + + +cmParsePythonCoverage::cmParsePythonCoverage( + cmCTestCoverageHandlerContainer& cont, + cmCTest* ctest) + :Coverage(cont), CTest(ctest) +{ +} + +bool cmParsePythonCoverage::ReadCoverageXML(const char* xmlFile) +{ + cmParsePythonCoverage::XMLParser parser(this->CTest, this->Coverage); + parser.ParseFile(xmlFile); + return true; +} diff --git a/Source/CTest/cmParsePythonCoverage.h b/Source/CTest/cmParsePythonCoverage.h new file mode 100644 index 0000000000..668c7f905f --- /dev/null +++ b/Source/CTest/cmParsePythonCoverage.h @@ -0,0 +1,48 @@ +/*============================================================================ + CMake - Cross Platform Makefile Generator + Copyright 2000-2009 Kitware, Inc. + + Distributed under the OSI-approved BSD License (the "License"); + see accompanying file Copyright.txt for details. + + This software is distributed WITHOUT ANY WARRANTY; without even the + implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the License for more information. +============================================================================*/ + +#ifndef cmParsePythonCoverage_h +#define cmParsePythonCoverage_h + +#include "cmStandardIncludes.h" +#include "cmCTestCoverageHandler.h" + +/** \class cmParsePythonCoverage + * \brief Parse coverage.py Python coverage information + * + * This class is used to parse the output of the coverage.py tool that + * is currently maintained by Ned Batchelder. That tool has a command + * that produces xml output in the format typically output by the common + * Java-based Cobertura coverage application. This helper class parses + * that XML file to fill the coverage-handler container. + */ +class cmParsePythonCoverage +{ +public: + + //! Create the coverage parser by passing in the coverage handler + //! container and the cmCTest object + cmParsePythonCoverage(cmCTestCoverageHandlerContainer& cont, + cmCTest* ctest); + + //! Read the XML produced by running `coverage xml` + bool ReadCoverageXML(const char* xmlFile); + +private: + + class XMLParser; + cmCTestCoverageHandlerContainer& Coverage; + cmCTest* CTest; + std::string CurFileName; +}; + +#endif diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 9c3ed594f7..030137c897 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -1959,6 +1959,25 @@ ${CMake_BINARY_DIR}/bin/cmake -DVERSION=master -P ${CMake_SOURCE_DIR}/Utilities/ PASS_REGULAR_EXPRESSION "Process file.*XINDEX.m.*Total LOC:.*125.*Percentage Coverage: 85.60.*" ENVIRONMENT COVFILE=) + + # Adding a test case for Python Coverage + configure_file( + "${CMake_SOURCE_DIR}/Tests/PythonCoverage/coverage.xml.in" + "${CMake_BINARY_DIR}/Testing/PythonCoverage/coverage.xml") + configure_file( + "${CMake_SOURCE_DIR}/Tests/PythonCoverage/DartConfiguration.tcl.in" + "${CMake_BINARY_DIR}/Testing/PythonCoverage/DartConfiguration.tcl") + file(COPY "${CMake_SOURCE_DIR}/Tests/PythonCoverage/coveragetest" + DESTINATION "${CMake_BINARY_DIR}/Testing/PythonCoverage") + add_test(NAME CTestPythonCoverage + COMMAND cmake -E chdir + ${CMake_BINARY_DIR}/Testing/PythonCoverage + $ -T Coverage --debug) + set_tests_properties(CTestPythonCoverage PROPERTIES + PASS_REGULAR_EXPRESSION + "Process file.*foo.py.*Total LOC:.*13.*Percentage Coverage: 84.62.*" + ENVIRONMENT COVFILE=) + # Use macro, not function so that build can still be driven by CMake 2.4. # After 2.6 is required, this could be a function without the extra 'set' # calls. diff --git a/Tests/PythonCoverage/DartConfiguration.tcl.in b/Tests/PythonCoverage/DartConfiguration.tcl.in new file mode 100644 index 0000000000..e29cffe4f9 --- /dev/null +++ b/Tests/PythonCoverage/DartConfiguration.tcl.in @@ -0,0 +1,8 @@ +# This file is configured by CMake automatically as DartConfiguration.tcl +# If you choose not to use CMake, this file may be hand configured, by +# filling in the required variables. + + +# Configuration directories and files +SourceDirectory: ${CMake_BINARY_DIR}/Testing/PythonCoverage/coveragetest +BuildDirectory: ${CMake_BINARY_DIR}/Testing/PythonCoverage diff --git a/Tests/PythonCoverage/coverage.xml.in b/Tests/PythonCoverage/coverage.xml.in new file mode 100644 index 0000000000..fcc1b1c67d --- /dev/null +++ b/Tests/PythonCoverage/coverage.xml.in @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/PythonCoverage/coveragetest/foo.py b/Tests/PythonCoverage/coveragetest/foo.py new file mode 100644 index 0000000000..97b5a41e9d --- /dev/null +++ b/Tests/PythonCoverage/coveragetest/foo.py @@ -0,0 +1,8 @@ + +def foo(): + x = 3 + 3 + return x + +def bar(): + y = 2 + 2 + return y diff --git a/Tests/PythonCoverage/coveragetest/test_foo.py b/Tests/PythonCoverage/coveragetest/test_foo.py new file mode 100644 index 0000000000..51a69d89e1 --- /dev/null +++ b/Tests/PythonCoverage/coveragetest/test_foo.py @@ -0,0 +1,11 @@ + +import foo +import unittest + +class TestFoo(unittest.TestCase): + + def testFoo(self): + self.assertEquals(foo.foo(), 6, 'foo() == 6') + +if __name__ == '__main__': + unittest.main()