Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow passing a custom demangler #39

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ python lcov_cobertura.py lcov-file.dat
- `-b/--base-dir` - (Optional) Directory where source files are located. Defaults to the current directory
- `-e/--excludes` - (Optional) Comma-separated list of regexes of packages to exclude
- `-o/--output` - (Optional) Path to store cobertura xml file. _Defaults to ./coverage.xml_
- `-d/--demangle` - (Optional) Demangle C++ function names. _Requires c++filt_
- `-D/--demangler=DEMANGLER` - (Optional) Demangle function names using DEMANGLER
- `-d/--demangle` - (Optional) Shortcut for `--demangler=c++filt`

```bash
python lcov_cobertura.py lcov-file.dat --base-dir src/dir --excludes test.lib --output build/coverage.xml --demangle
Expand All @@ -43,7 +44,8 @@ lcov_cobertura lcov-file.dat
- `-b/--base-dir` - (Optional) Directory where source files are located. Defaults to the current directory
- `-e/--excludes` - (Optional) Comma-separated list of regexes of packages to exclude
- `-o/--output` - (Optional) Path to store cobertura xml file. _Defaults to ./coverage.xml_
- `-d/--demangle` - (Optional) Demangle C++ function names. _Requires c++filt_
- `-D/--demangler=DEMANGLER` - (Optional) Demangle function names using DEMANGLER
- `-d/--demangle` - (Optional) Shortcut for `--demangler=c++filt`

```bash
lcov_cobertura lcov-file.dat --base-dir src/dir --excludes test.lib --output build/coverage.xml --demangle
Expand Down
36 changes: 20 additions & 16 deletions lcov_cobertura/lcov_cobertura.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,12 @@
__version__ = '2.0.1'

CPPFILT = "c++filt"
HAVE_CPPFILT = False

if find_executable(CPPFILT) is not None:
HAVE_CPPFILT = True


class Demangler():
def __init__(self):
class Demangler(object):
def __init__(self, demangler):
self.pipe = subprocess.Popen( # nosec - not for untrusted input
[CPPFILT], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
[demangler], stdin=subprocess.PIPE, stdout=subprocess.PIPE)

def demangle(self, name):
newname = name + "\n"
Expand Down Expand Up @@ -64,7 +60,8 @@ class LcovCobertura():
...
"""

def __init__(self, lcov_data, base_dir='.', excludes=None, demangle=False):
def __init__(self, lcov_data, base_dir='.', excludes=None,
demangler=None):
"""
Create a new :class:`LcovCobertura` object using the given `lcov_data`
and `options`.
Expand All @@ -75,17 +72,17 @@ def __init__(self, lcov_data, base_dir='.', excludes=None, demangle=False):
:type base_dir: string
:param excludes: list of regexes to packages as excluded
:type excludes: [string]
:param demangle: whether to demangle function names using c++filt
:type demangle: bool
:param demangler: program to demangle function names, or None
:type demangler: string
"""

if not excludes:
excludes = []
self.lcov_data = lcov_data
self.base_dir = base_dir
self.excludes = excludes
if demangle:
demangler = Demangler()
if demangler:
demangler = Demangler(demangler)
self.format = demangler.demangle
else:
self.format = lambda x: x
Expand Down Expand Up @@ -401,27 +398,34 @@ def main(argv=None):
help='Path to store cobertura xml file',
action='store', dest='output', default='coverage.xml')
parser.add_option('-d', '--demangle',
help='Demangle C++ function names using %s' % CPPFILT,
help='Demangle function names using %s' % CPPFILT,
action='store_true', dest='demangle', default=False)
parser.add_option('-D', '--demangler',
help='Demangle function names using DEMANGLER',
action='store', dest='demangler', default=None)
parser.add_option('-v', '--version',
help='Display version info',
action='store_true')
(options, args) = parser.parse_args(args=argv)

if options.demangle and not HAVE_CPPFILT:
raise RuntimeError("C++ filter executable (%s) not found!" % CPPFILT)
if options.version:
print('[lcov_cobertura {}]'.format(__version__))
sys.exit(0)

if options.demangle and options.demangler is None:
options.demangler = CPPFILT

if options.demangler and not find_executable(options.demangler):
raise RuntimeError("filter executable (%s) not found!" % options.demangler)

if len(args) != 2:
print(main.__doc__)
sys.exit(1)

try:
with open(args[1], 'r') as lcov_file:
lcov_data = lcov_file.read()
lcov_cobertura = LcovCobertura(lcov_data, options.base_dir, options.excludes, options.demangle)
lcov_cobertura = LcovCobertura(lcov_data, options.base_dir, options.excludes, options.demangler)
cobertura_xml = lcov_cobertura.convert()
with open(options.output, mode='wt') as output_file:
output_file.write(cobertura_xml)
Expand Down
16 changes: 16 additions & 0 deletions test/mockrustfilt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env python

import sys

demanglemap = {
"_RNvCsie3AuTHCqpB_10rust_hello4calc": "rust_hello::calc",
"_RNvCsie3AuTHCqpB_10rust_hello4main": "rust_hello::main",
}

def main():
while True:
line = sys.stdin.readline().strip()
print(demanglemap[line], flush=True)

if __name__ == "__main__":
main()
80 changes: 79 additions & 1 deletion test/test_lcov_cobertura.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# This is free software, licensed under the Apache License, Version 2.0,
# available in the accompanying LICENSE.txt file.

import os
import unittest
from xmldiff import main as xmldiff

Expand Down Expand Up @@ -130,7 +131,7 @@ def test_support_function_names_with_commas(self):
def test_demangle(self):
converter = LcovCobertura(
"TN:\nSF:foo/foo.cpp\nFN:3,_ZN3Foo6answerEv\nFNDA:1,_ZN3Foo6answerEv\nFN:8,_ZN3Foo3sqrEi\nFNDA:1,_ZN3Foo3sqrEi\nDA:3,1\nDA:5,1\nDA:8,1\nDA:10,1\nend_of_record",
demangle=True)
demangler="c++filt")
TEST_TIMESTAMP = 1594850794
TEST_XML = r"""<?xml version="1.0" ?>
<!DOCTYPE coverage
Expand Down Expand Up @@ -167,10 +168,87 @@ def test_demangle(self):
</packages>
</coverage>
""".format(TEST_TIMESTAMP)
result = converter.parse(timestamp=TEST_TIMESTAMP)
xml = converter.generate_cobertura_xml(result, indent=" ")
xml_diff = xmldiff.diff_texts(TEST_XML, xml)
self.assertEqual(len(xml_diff), 0)

def test_custom_demangler(self):
# custom mock demangler script in same folder as this file
demangler = "{}/mockrustfilt".format(os.path.dirname(os.path.realpath(__file__)))

converter = LcovCobertura("""\
SF:src/main.rs
FN:6,_RNvCsie3AuTHCqpB_10rust_hello4calc
FN:2,_RNvCsie3AuTHCqpB_10rust_hello4main
FNDA:1,_RNvCsie3AuTHCqpB_10rust_hello4calc
FNDA:1,_RNvCsie3AuTHCqpB_10rust_hello4main
FNF:2
FNH:2
DA:2,1
DA:3,1
DA:4,1
DA:6,1
DA:7,1
DA:8,0
DA:9,1
DA:10,1
DA:11,1
DA:12,1
BRF:0
BFH:0
LF:10
LH:9
end_of_record""",
demangler=demangler)
TEST_TIMESTAMP = 1594850794

TEST_XML = """\
<?xml version="1.0" ?>
<!DOCTYPE coverage
SYSTEM 'http://cobertura.sourceforge.net/xml/coverage-04.dtd'>
<coverage branch-rate="0.0" branches-covered="0" branches-valid="0" complexity="0" line-rate="0.9" lines-covered="9" lines-valid="10" timestamp="{TEST_TIMESTAMP}" version="2.0.3">
<sources>
<source>.</source>
</sources>
<packages>
<package branch-rate="0.0" complexity="0" line-rate="0.9" name="src">
<classes>
<class branch-rate="0.0" complexity="0" filename="src/main.rs" line-rate="0.9" name="src.main.rs">
<methods>
<method branch-rate="1.0" line-rate="1.0" name="rust_hello::calc" signature="">
<lines>
<line branch="false" hits="1" number="6"/>
</lines>
</method>
<method branch-rate="1.0" line-rate="1.0" name="rust_hello::main" signature="">
<lines>
<line branch="false" hits="1" number="2"/>
</lines>
</method>
</methods>
<lines>
<line branch="false" hits="1" number="2"/>
<line branch="false" hits="1" number="3"/>
<line branch="false" hits="1" number="4"/>
<line branch="false" hits="1" number="6"/>
<line branch="false" hits="1" number="7"/>
<line branch="false" hits="0" number="8"/>
<line branch="false" hits="1" number="9"/>
<line branch="false" hits="1" number="10"/>
<line branch="false" hits="1" number="11"/>
<line branch="false" hits="1" number="12"/>
</lines>
</class>
</classes>
</package>
</packages>
</coverage>""".format(TEST_TIMESTAMP=TEST_TIMESTAMP)
result = converter.parse(timestamp=TEST_TIMESTAMP)
xml = converter.generate_cobertura_xml(result, indent=" ")
xml_diff = xmldiff.diff_texts(xml, TEST_XML)
self.assertEqual(len(xml_diff), 0)


if __name__ == '__main__':
unittest.main(verbosity=2)