diff --git a/README.md b/README.md index 30e3429..e06cd75 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 diff --git a/lcov_cobertura/lcov_cobertura.py b/lcov_cobertura/lcov_cobertura.py index 15783b3..693adfa 100755 --- a/lcov_cobertura/lcov_cobertura.py +++ b/lcov_cobertura/lcov_cobertura.py @@ -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" @@ -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`. @@ -75,8 +72,8 @@ 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: @@ -84,8 +81,8 @@ def __init__(self, lcov_data, base_dir='.', excludes=None, demangle=False): 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 @@ -401,19 +398,26 @@ 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) @@ -421,7 +425,7 @@ def main(argv=None): 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) diff --git a/test/mockrustfilt b/test/mockrustfilt new file mode 100755 index 0000000..c7d9d2a --- /dev/null +++ b/test/mockrustfilt @@ -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() diff --git a/test/test_lcov_cobertura.py b/test/test_lcov_cobertura.py index 55d0489..d498de4 100755 --- a/test/test_lcov_cobertura.py +++ b/test/test_lcov_cobertura.py @@ -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 @@ -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""" """.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 = """\ + + + + + . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""".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)