Commit d0ec3a01 authored by Patrick Reynolds's avatar Patrick Reynolds Committed by Brad King
Browse files

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.
parent 6ed8504e
......@@ -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
......
......@@ -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)
......
......@@ -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);
......
#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;
}
/*============================================================================
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
......@@ -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.
......
# 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
<?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>
def foo():
x = 3 + 3
return x
def bar():
y = 2 + 2
return y
import foo
import unittest
class TestFoo(unittest.TestCase):
def testFoo(self):
self.assertEquals(foo.foo(), 6, 'foo() == 6')
if __name__ == '__main__':
unittest.main()
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment