Adding support for the Python coverage.py tool.

This assumes that coverage.py has been run in such a way to produce its
standard XML output. This uses the Cobertura schema and should be somewhat
generalizable.
This commit is contained in:
Patrick Reynolds 2013-09-27 10:42:39 -04:00 committed by Brad King
parent 6ed8504ea5
commit d0ec3a01a6
10 changed files with 281 additions and 0 deletions

View File

@ -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

View File

@ -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<int>(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<int>(cont->TotalCoverage.size());
}
//----------------------------------------------------------------------
int cmCTestCoverageHandler::HandleMumpsCoverage(
cmCTestCoverageHandlerContainer* cont)

View File

@ -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);

View File

@ -0,0 +1,113 @@
#include "cmStandardIncludes.h"
#include "cmSystemTools.h"
#include "cmXMLParser.h"
#include "cmParsePythonCoverage.h"
#include <cmsys/Directory.hxx>
//----------------------------------------------------------------------------
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;
}

View File

@ -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

View File

@ -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
$<TARGET_FILE:ctest> -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.

View File

@ -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

View File

@ -0,0 +1,35 @@
<?xml version="1.0" ?>
<!DOCTYPE coverage
SYSTEM 'http://cobertura.sourceforge.net/xml/coverage-03.dtd'>
<coverage branch-rate="0" line-rate="0.8462" timestamp="1380469411433" version="3.6">
<!-- Generated by coverage.py: http://nedbatchelder.com/code/coverage -->
<packages>
<package branch-rate="0" complexity="0" line-rate="0.8462" name="">
<classes>
<class branch-rate="0" complexity="0" filename="foo.py" line-rate="0.6667" name="foo">
<methods/>
<lines>
<line hits="1" number="2"/>
<line hits="1" number="3"/>
<line hits="1" number="4"/>
<line hits="1" number="6"/>
<line hits="0" number="7"/>
<line hits="0" number="8"/>
</lines>
</class>
<class branch-rate="0" complexity="0" filename="test_foo.py" line-rate="1" name="test_foo">
<methods/>
<lines>
<line hits="1" number="2"/>
<line hits="1" number="3"/>
<line hits="1" number="5"/>
<line hits="1" number="7"/>
<line hits="1" number="8"/>
<line hits="1" number="10"/>
<line hits="1" number="11"/>
</lines>
</class>
</classes>
</package>
</packages>
</coverage>

View File

@ -0,0 +1,8 @@
def foo():
x = 3 + 3
return x
def bar():
y = 2 + 2
return y

View File

@ -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()