├── docs ├── source │ ├── README.md │ ├── index.rst │ └── conf.py ├── Makefile └── make.bat ├── RELEASE ├── setup.py ├── lcov_cobertura ├── __main__.py ├── __init__.py └── lcov_cobertura.py ├── .gitignore ├── requirements-dev.txt ├── .gitchangelog-keepachangelog.tpl ├── CHANGELOG.md ├── CONTRIBUTING.md ├── .github └── workflows │ ├── bandit.yml │ ├── sphinx.yml │ ├── ci.yml │ └── release.yml ├── pyproject.toml ├── setup.cfg ├── README.md ├── tox.ini ├── test └── test_lcov_cobertura.py ├── .gitchangelog.rc ├── LICENSE └── .pylintrc /docs/source/README.md: -------------------------------------------------------------------------------- 1 | ../../README.md -------------------------------------------------------------------------------- /RELEASE: -------------------------------------------------------------------------------- 1 | make release 2 | make pypi 3 | 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | setuptools.setup() 4 | -------------------------------------------------------------------------------- /lcov_cobertura/__main__.py: -------------------------------------------------------------------------------- 1 | from lcov_cobertura.lcov_cobertura import main 2 | 3 | if __name__ == "__main__": 4 | main() 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | MANIFEST 2 | .idea 3 | build 4 | dist 5 | *.pyc 6 | *.cache 7 | *.egg-info 8 | 9 | docs/_build 10 | docs/source/api 11 | 12 | .coverage 13 | coverage.xml 14 | nosetests.xml 15 | lcov_cobertura.egg-info 16 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | # python development/tools requirements file 2 | pre-commit 3 | pylint 4 | bandit 5 | flake8 6 | pycodestyle 7 | pytest 8 | xmldiff 9 | pytest-cov 10 | coverage 11 | coverage_python_version 12 | -------------------------------------------------------------------------------- /lcov_cobertura/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Coverage conversion tool to make lcov output compatible with tools that 3 | expect Cobertura/coverage.py format. 4 | """ 5 | from lcov_cobertura.lcov_cobertura import LcovCobertura 6 | 7 | 8 | __all__ = ["LcovCobertura"] 9 | -------------------------------------------------------------------------------- /.gitchangelog-keepachangelog.tpl: -------------------------------------------------------------------------------- 1 | # ChangeLog 2 | 3 | {{#versions}} 4 | ## {{#tag}}{{{tag}}}{{/tag}}{{^tag}}_(unreleased)_{{/tag}} 5 | 6 | {{#sections}} 7 | ### {{{label}}} 8 | 9 | {{#commits}} 10 | - {{{subject}}} [{{{author}}}] 11 | {{#body}} 12 | 13 | {{{body_indented}}} 14 | {{/body}} 15 | {{/commits}} 16 | 17 | {{/sections}} 18 | 19 | {{/versions}} 20 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to lcov_cobertura's documentation! 2 | ========================================== 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | :caption: Contents: 7 | 8 | README.md 9 | api/modules 10 | 11 | 12 | Indices and tables 13 | ================== 14 | 15 | * :ref:`genindex` 16 | * :ref:`modindex` 17 | * :ref:`search` 18 | 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Version 2.0.0 2 | 3 | - Drop support for Python 2.x 4 | - Bug fixes 5 | 6 | # Version 1.6 7 | 8 | - Support for method coverage 9 | - Update to cobertura DTD v4 10 | 11 | # Version 1.4 12 | 13 | - Support source code outside the workspace in Jenkins 14 | 15 | # Version 1.2 16 | 17 | - Function coverage reporting 18 | 19 | # Version 1.1 20 | 21 | - Branch coverage reporting 22 | - Python 3 support 23 | 24 | # Version 1.0 25 | 26 | - Initial release with package, class, and line coverage 27 | 28 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | When submitting pull requests, please do the following to make it easier to incorporate your changes: 2 | 3 | * Include unit and/or functional tests that validate changes you're making. 4 | * Rebase your changes onto the HEAD of my fork if you can do so cleanly. 5 | * If submitting additional functionality, provide an example of how to use it. 6 | * Please keep code style consistent with surrounding code. 7 | 8 | ## Testing 9 | You can run all tests by simply running `pytest` from your favorite shell. 10 | 11 | Please make sure your tests run on Python 3.8. 12 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /.github/workflows/bandit.yml: -------------------------------------------------------------------------------- 1 | name: Security check - Bandit 2 | 3 | on: 4 | push: 5 | branches: [ "*" ] 6 | 7 | permissions: 8 | contents: read 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-22.04 13 | permissions: 14 | checks: write # for bandit-report-artifacts to publish the checks 15 | contents: read # for actions/checkout to fetch code 16 | security-events: write # for bandit-report-artifacts to upload results 17 | actions: read # only on private (maybe?) required to get the Action run status 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - name: Run bandit 23 | uses: VCTLabs/bandit-report-artifacts@master 24 | with: 25 | project_path: lcov_cobertura 26 | ignore_failure: false 27 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.https://www.sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=45", 4 | "setuptools_scm[toml]>=6.2", 5 | ] 6 | build-backend = "setuptools.build_meta" 7 | 8 | [tool.setuptools_scm] 9 | 10 | [tool.pytest.ini_options] 11 | minversion = "6.0" 12 | testpaths = ["test",] 13 | log_cli = false 14 | doctest_optionflags = ["ELLIPSIS", "NORMALIZE_WHITESPACE",] 15 | addopts = "--strict-markers" 16 | markers = "subscript" 17 | 18 | [tool.coverage.run] 19 | branch = true 20 | source = ["lcov_cobertura"] 21 | omit = [ 22 | "test", 23 | ".tox", 24 | ] 25 | 26 | [tool.coverage.paths] 27 | source = ["lcov_cobertura"] 28 | 29 | [tool.coverage.report] 30 | fail_under = 70 31 | show_missing = true 32 | 33 | [tool.black] 34 | line-length = 107 35 | skip-string-normalization = true 36 | include = '\.py$' 37 | exclude = ''' 38 | /( 39 | \.git 40 | | \.hg 41 | | \.mypy_cache 42 | | \.tox 43 | | \.venv 44 | | _build 45 | | buck-out 46 | | build 47 | | dist 48 | | test 49 | )/ 50 | ''' 51 | -------------------------------------------------------------------------------- /.github/workflows/sphinx.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-22.04 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 0 17 | 18 | - uses: actions/setup-python@v5 19 | with: 20 | python-version: '3.11' 21 | 22 | - name: Add python requirements 23 | run: | 24 | python -m pip install --upgrade pip 25 | pip install tox tox-gh-actions 26 | 27 | - name: Build docs 28 | run: | 29 | # tox -e docs-lint 30 | tox -e docs 31 | 32 | - uses: actions/upload-artifact@v4 33 | with: 34 | name: ApiDocsHTML 35 | path: "docs/_build/html/" 36 | 37 | - name: set nojekyll for github 38 | run: | 39 | sudo touch docs/_build/html/.nojekyll 40 | 41 | - name: Deploy docs to gh-pages 42 | if: ${{ github.event_name == 'push' }} 43 | uses: JamesIves/github-pages-deploy-action@v4 44 | with: 45 | folder: docs/_build/html/ 46 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [ master, release ] 7 | pull_request: 8 | branches: [ "*" ] 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: ${{ matrix.os }} 14 | defaults: 15 | run: 16 | shell: bash 17 | env: 18 | OS: ${{ matrix.os }} 19 | PYTHON: ${{ matrix.python-version }} 20 | PYTHONIOENCODING: utf-8 21 | PIP_DOWNLOAD_CACHE: ${{ github.workspace }}/../.pip_download_cache 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | os: [ubuntu-22.04] 26 | python-version: ['3.9', '3.10', '3.11', '3.12'] 27 | steps: 28 | - name: Set git crlf/eol 29 | run: | 30 | git config --global core.autocrlf false 31 | git config --global core.eol lf 32 | 33 | - uses: actions/checkout@v4 34 | with: 35 | fetch-depth: 0 36 | 37 | - name: Set up Python ${{ matrix.python-version }} 38 | uses: actions/setup-python@v5 39 | with: 40 | python-version: ${{ matrix.python-version }} 41 | 42 | - name: Install dependencies 43 | run: | 44 | python -m pip install --upgrade pip 45 | pip install tox tox-gh-actions 46 | 47 | - name: Run tests 48 | run: | 49 | tox 50 | env: 51 | PLATFORM: ${{ matrix.os }} 52 | 53 | - name: Check pkg builds 54 | run: | 55 | tox -e deploy,check 56 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = lcov_cobertura 3 | version = attr: setuptools_scm.get_version 4 | description = LCOV to Cobertura XML converter 5 | url = https://eriwen.github.io/lcov-to-cobertura-xml/ 6 | author = Eric Wendelin 7 | author_email = me@eriwen.com 8 | maintainer = Steve Arnold 9 | maintainer_email = nerdboy@gentoo.org 10 | long_description = file: README.md 11 | long_description_content_type = text/markdown 12 | download_url = https://raw.githubusercontent.com/eriwen/lcov-to-cobertura-xml/master/lcov_cobertura/lcov_cobertura.py 13 | license = Apache License, Version 2.0 14 | license_expression = Apache-2.0 15 | license_files = LICENSE 16 | classifiers = 17 | Programming Language :: Python 18 | Intended Audience :: Developers 19 | Natural Language :: English 20 | License :: OSI Approved :: Apache Software License 21 | Operating System :: OS Independent 22 | Topic :: Software Development :: Testing 23 | Topic :: Software Development :: Quality Assurance 24 | Development Status :: 5 - Production/Stable 25 | Programming Language :: Python :: 3 26 | Programming Language :: Python :: 3.8 27 | Programming Language :: Python :: 3.9 28 | 29 | keywords = 30 | lcov 31 | cobertura 32 | 33 | [options] 34 | python_requires = >=3.8 35 | 36 | setup_requires = 37 | setuptools_scm[toml] 38 | 39 | install_requires = 40 | importlib-metadata; python_version < '3.8' 41 | 42 | packages = 43 | lcov_cobertura 44 | 45 | [options.entry_points] 46 | console_scripts = 47 | lcov_cobertura = lcov_cobertura.lcov_cobertura:main 48 | 49 | # deps are included here mainly for local/venv installs using pip 50 | # otherwise deps are handled via tox, ci config files or pkg managers 51 | [options.extras_require] 52 | doc = 53 | sphinx 54 | recommonmark 55 | sphinx_rtd_theme 56 | sphinxcontrib-apidoc 57 | 58 | test = 59 | pytest 60 | xmldiff 61 | 62 | cov = 63 | pytest-cov 64 | coverage[toml] 65 | 66 | all = 67 | %(cov)s 68 | %(doc)s 69 | %(test)s 70 | 71 | [check] 72 | metadata = true 73 | restructuredtext = true 74 | strict = false 75 | 76 | [check-manifest] 77 | ignore = 78 | .gitattributes 79 | .gitignore 80 | .pre-commit-config.yaml 81 | 82 | [flake8] 83 | exclude = 84 | .git, 85 | __pycache__, 86 | build, 87 | dist 88 | test 89 | 90 | max-line-length = 107 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lcov to cobertura XML converter 2 | 3 | [![CI](https://github.com/eriwen/lcov-to-cobertura-xml/actions/workflows/ci.yml/badge.svg)](https://github.com/eriwen/lcov-to-cobertura-xml/actions/workflows/ci.yml) 4 | [![Docs](https://github.com/eriwen/lcov-to-cobertura-xml/actions/workflows/sphinx.yml/badge.svg)](https://github.com/eriwen/lcov-to-cobertura-xml/actions/workflows/sphinx.yml) 5 | [![Security check - Bandit](https://github.com/eriwen/lcov-to-cobertura-xml/actions/workflows/bandit.yml/badge.svg)](https://github.com/eriwen/lcov-to-cobertura-xml/actions/workflows/bandit.yml) 6 | [![Release](https://github.com/eriwen/lcov-to-cobertura-xml/actions/workflows/release.yml/badge.svg)](https://github.com/eriwen/lcov-to-cobertura-xml/actions/workflows/release.yml) 7 | 8 | This project does as the name implies: it converts code coverage report files in [lcov](http://ltp.sourceforge.net/coverage/lcov.php) format to [Cobertura](http://cobertura.sourceforge.net/)'s XML report format so that CI servers like [Jenkins](http://jenkins-ci.org) can aggregate results and determine build stability etc. 9 | 10 | Coverage metrics supported: 11 | 12 | - Package/folder overall line and branch coverage 13 | - Class/file overall line and branch coverage 14 | - Functions hit 15 | - Line and Branch hits 16 | 17 | ## Quick usage 18 | 19 | [Grab it raw](https://raw.github.com/eriwen/lcov-to-cobertura-xml/master/lcov_cobertura/lcov_cobertura.py) and run it with python: 20 | ```bash 21 | python lcov_cobertura.py lcov-file.dat 22 | ``` 23 | 24 | - `-b/--base-dir` - (Optional) Directory where source files are located. Defaults to the current directory 25 | - `-e/--excludes` - (Optional) Comma-separated list of regexes of packages to exclude 26 | - `-o/--output` - (Optional) Path to store cobertura xml file. _Defaults to ./coverage.xml_ 27 | - `-d/--demangle` - (Optional) Demangle C++ function names. _Requires c++filt_ 28 | 29 | ```bash 30 | python lcov_cobertura.py lcov-file.dat --base-dir src/dir --excludes test.lib --output build/coverage.xml --demangle 31 | ``` 32 | 33 | ## With [pip](http://pypi.python.org/pypi/pip): 34 | ```bash 35 | pip install lcov_cobertura 36 | ``` 37 | 38 | ### Command-line usage 39 | ```bash 40 | lcov_cobertura lcov-file.dat 41 | ``` 42 | 43 | - `-b/--base-dir` - (Optional) Directory where source files are located. Defaults to the current directory 44 | - `-e/--excludes` - (Optional) Comma-separated list of regexes of packages to exclude 45 | - `-o/--output` - (Optional) Path to store cobertura xml file. _Defaults to ./coverage.xml_ 46 | - `-d/--demangle` - (Optional) Demangle C++ function names. _Requires c++filt_ 47 | 48 | ```bash 49 | lcov_cobertura lcov-file.dat --base-dir src/dir --excludes test.lib --output build/coverage.xml --demangle 50 | ``` 51 | 52 | ### Usage as a Python module 53 | 54 | Use it anywhere in your python: 55 | ```python 56 | from lcov_cobertura import LcovCobertura 57 | 58 | LCOV_INPUT = 'SF:foo/file.ext\nDA:1,1\nDA:2,0\nend_of_record\n' 59 | converter = LcovCobertura(LCOV_INPUT) 60 | cobertura_xml = converter.convert() 61 | print(cobertura_xml) 62 | ``` 63 | 64 | ## Environment Support 65 | 66 | Python 3.8+ is supported. The last release with Python 2.x support is [version 1.6](https://pypi.org/project/lcov_cobertura/1.6/). 67 | 68 | ## Contributions 69 | This project is made possible due to the efforts of these fine people: 70 | 71 | - [Eric Wendelin](https://eriwen.com) 72 | - [Björge Dijkstra](https://github.com/bjd) 73 | - [Jon Schewe](http://mtu.net/~jpschewe) 74 | - [Yury V. Zaytsev](http://yury.zaytsev.net) 75 | - [Steve Arnold](https://github.com/sarnold) 76 | 77 | ## License 78 | This project is provided under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). 79 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py3{9,10,11,12,13}-{linux,macos,windows} 3 | skip_missing_interpreters = true 4 | isolated_build = true 5 | skipsdist = true 6 | 7 | [gh-actions] 8 | python = 9 | 3.9: py39 10 | 3.10: py310 11 | 3.11: py311 12 | 3.12: py312 13 | 3.13: py313 14 | 15 | [gh-actions:env] 16 | PLATFORM = 17 | ubuntu-22.04: linux 18 | macos-latest: macos 19 | windows-latest: windows 20 | 21 | [base] 22 | deps = 23 | pip>=20.3 24 | 25 | [build] 26 | deps = 27 | pip>=20.3 28 | wheel 29 | build 30 | twine 31 | 32 | [testenv] 33 | allowlist_externals = bash 34 | passenv = 35 | CI 36 | OS 37 | PYTHONIOENCODING 38 | PIP_DOWNLOAD_CACHE 39 | 40 | deps = 41 | {[base]deps} 42 | .[test,cov] 43 | 44 | commands = 45 | # use --capture=no to see test runner output/results 46 | python -m pytest -v test/ --capture=no --doctest-modules --cov lcov_cobertura --cov-branch --cov-report term-missing 47 | coverage xml 48 | #bash -c './.github/fix_pkg_name.sh' 49 | 50 | [testenv:docs] 51 | skip_install = true 52 | allowlist_externals = 53 | make 54 | 55 | deps = 56 | {[base]deps} 57 | .[doc] 58 | 59 | commands = make -C docs html 60 | 61 | [testenv:docs-clean] 62 | skip_install = true 63 | allowlist_externals = 64 | {[testenv:docs]allowlist_externals} 65 | 66 | deps = 67 | {[base]deps} 68 | .[doc] 69 | 70 | commands = make -C docs clean 71 | 72 | [testenv:docs-lint] 73 | skip_install = true 74 | allowlist_externals = 75 | make 76 | 77 | deps = 78 | {[base]deps} 79 | .[doc] 80 | 81 | commands = make -C docs linkcheck 82 | 83 | [testenv:lint] 84 | passenv = 85 | CI 86 | OS 87 | PIP_DOWNLOAD_CACHE 88 | 89 | deps = 90 | {[base]deps} 91 | pylint 92 | 93 | commands = 94 | pylint --fail-under=9.00 lcov_cobertura/lcov_cobertura.py 95 | 96 | [testenv:style] 97 | passenv = 98 | CI 99 | OS 100 | PIP_DOWNLOAD_CACHE 101 | 102 | deps = 103 | {[base]deps} 104 | flake8 105 | flake8-bugbear 106 | 107 | commands = 108 | flake8 lcov_cobertura/ 109 | 110 | [testenv:deploy] 111 | skip_install = true 112 | passenv = 113 | pythonLocation 114 | CI 115 | PYTHONIOENCODING 116 | PIP_DOWNLOAD_CACHE 117 | 118 | deps = 119 | {[build]deps} 120 | 121 | commands = 122 | python -m build . 123 | twine check dist/* 124 | 125 | [testenv:check] 126 | skip_install = true 127 | passenv = CI 128 | 129 | allowlist_externals = bash 130 | 131 | deps = 132 | {[base]deps} 133 | 134 | commands = 135 | bash -c 'export WHL_FILE=$(find . -maxdepth 2 -name \*.whl); \ 136 | python -m pip install --force-reinstall $WHL_FILE' 137 | python -m pip show lcov_cobertura 138 | 139 | [testenv:mypy] 140 | skip_install = true 141 | 142 | setenv = PYTHONPATH = {toxinidir} 143 | 144 | deps = 145 | {[base]deps} 146 | mypy 147 | 148 | commands = 149 | python -m mypy --follow-imports=normal --install-types --non-interactive lcov_cobertura/ 150 | 151 | [testenv:changes] 152 | skip_install = true 153 | 154 | allowlist_externals = /bin/bash 155 | 156 | deps = 157 | {[base]deps} 158 | gitchangelog @ https://github.com/sarnold/gitchangelog/releases/download/3.0.8/gitchangelog-3.0.8-py3-none-any.whl 159 | 160 | commands = 161 | bash -c 'export GITCHANGELOG_CONFIG_FILENAME=".gitchangelog.rc"; \ 162 | gitchangelog $(git tag --sort=taggerdate | tail -n2 | head -n1)..' 163 | 164 | [testenv:clean] 165 | skip_install = true 166 | allowlist_externals = 167 | bash 168 | 169 | deps = 170 | pip>=21.1 171 | 172 | commands = 173 | bash -c 'rm -rf *.egg-info dump.* lcov_cobertura/__pycache__ dist/ build/ docs/source/api/' 174 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | # release on tag push 6 | tags: 7 | - '*' 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ${{ matrix.os }} 13 | defaults: 14 | run: 15 | shell: bash 16 | env: 17 | PYTHONIOENCODING: utf-8 18 | strategy: 19 | fail-fast: true 20 | matrix: 21 | os: [ubuntu-22.04] 22 | python-version: ['3.9', '3.10', '3.11', '3.12'] 23 | 24 | steps: 25 | - name: Set git crlf/eol 26 | run: | 27 | git config --global core.autocrlf false 28 | git config --global core.eol lf 29 | 30 | - uses: actions/checkout@v4 31 | with: 32 | fetch-depth: 0 33 | 34 | - name: Set up Python ${{ matrix.python-version }} 35 | uses: actions/setup-python@v5 36 | with: 37 | python-version: ${{ matrix.python-version }} 38 | 39 | - name: Install dependencies 40 | run: | 41 | python -m pip install --upgrade pip wheel 42 | pip install tox 43 | 44 | - name: Build dist pkgs 45 | run: | 46 | tox -e deploy 47 | 48 | - name: Upload artifacts 49 | if: matrix.python-version == 3.9 && runner.os == 'Linux' 50 | uses: actions/upload-artifact@v4 51 | with: 52 | name: packages 53 | path: dist 54 | 55 | create_release: 56 | name: Create Release 57 | needs: [build] 58 | environment: PyPI 59 | runs-on: ubuntu-20.04 60 | 61 | steps: 62 | - name: Get version 63 | id: get_version 64 | run: | 65 | echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV 66 | echo ${{ env.VERSION }} 67 | 68 | - uses: actions/checkout@v4 69 | with: 70 | fetch-depth: 0 71 | 72 | # download all artifacts to project dir 73 | - uses: actions/download-artifact@v4 74 | 75 | - name: check artifacts 76 | run: | 77 | ls -l packages/ 78 | 79 | - name: Generate changes file 80 | uses: sarnold/gitchangelog-action@v1 81 | with: 82 | github_token: ${{ secrets.GITHUB_TOKEN}} 83 | 84 | - name: Create release 85 | id: create_release 86 | uses: softprops/action-gh-release@v1 87 | env: 88 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 89 | with: 90 | tag_name: ${{ env.VERSION }} 91 | name: Release v${{ env.VERSION }} 92 | body_path: CHANGES.md 93 | draft: false 94 | prerelease: false 95 | files: | 96 | packages/* 97 | 98 | - name: Publish a Python distribution to PyPI 99 | uses: pypa/gh-action-pypi-publish@release/v1 100 | with: 101 | user: __token__ 102 | password: ${{ secrets.PYPI_API_TOKEN }} 103 | packages_dir: packages/ 104 | 105 | docs: 106 | name: Release docs 107 | needs: [create_release] 108 | runs-on: ubuntu-20.04 109 | 110 | steps: 111 | - uses: actions/checkout@v4 112 | with: 113 | fetch-depth: 0 114 | 115 | - uses: actions/setup-python@v5 116 | with: 117 | python-version: '3.9' 118 | 119 | - name: Add python requirements 120 | run: | 121 | python -m pip install --upgrade pip 122 | pip install tox 123 | 124 | - name: Build docs 125 | run: | 126 | tox -e docs 127 | 128 | - uses: actions/upload-artifact@v4 129 | with: 130 | name: ApiDocsHTML 131 | path: "docs/_build/html/" 132 | 133 | - name: set nojekyll for github 134 | run: | 135 | sudo touch docs/_build/html/.nojekyll 136 | 137 | - name: Deploy docs to gh-pages 138 | if: ${{ github.event_name == 'push' }} 139 | uses: JamesIves/github-pages-deploy-action@v4 140 | with: 141 | folder: docs/_build/html/ 142 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | 16 | if sys.version_info < (3, 8): 17 | from importlib_metadata import version 18 | else: 19 | from importlib.metadata import version 20 | 21 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))) 22 | 23 | __version__ = version('lcov_cobertura') 24 | 25 | # -- Project information ----------------------------------------------------- 26 | 27 | project = 'lcov_cobertura' 28 | copyright = '2012, Eric Wendelin' 29 | author = 'Eric Wendelin' 30 | 31 | # The full version, including alpha/beta/rc tags 32 | version = __version__ 33 | release = version 34 | 35 | 36 | # -- General configuration --------------------------------------------------- 37 | 38 | # Add any Sphinx extension module names here, as strings. They can be 39 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 40 | # ones. 41 | extensions = [ 42 | 'sphinxcontrib.apidoc', 43 | 'sphinx.ext.autodoc', 44 | 'sphinx.ext.doctest', 45 | 'sphinx.ext.todo', 46 | 'sphinx.ext.coverage', 47 | 'sphinx.ext.viewcode', 48 | 'recommonmark', 49 | ] 50 | 51 | apidoc_module_dir = '../../lcov_cobertura/' 52 | apidoc_output_dir = 'api' 53 | apidoc_excluded_paths = ['test'] 54 | apidoc_separate_modules = True 55 | 56 | # Add any paths that contain templates here, relative to this directory. 57 | templates_path = ['_templates'] 58 | 59 | # The master toctree document. 60 | master_doc = 'index' 61 | 62 | # The suffix(es) of source filenames. 63 | # You can specify multiple suffix as a list of string: 64 | # source_suffix = ['.rst', '.md'] 65 | source_suffix = '.rst' 66 | 67 | # List of patterns, relative to source directory, that match files and 68 | # directories to ignore when looking for source files. 69 | # This pattern also affects html_static_path and html_extra_path. 70 | exclude_patterns = ['_build'] 71 | 72 | # If true, `todo` and `todoList` produce output, else they produce nothing. 73 | todo_include_todos = True 74 | 75 | # The name of the Pygments (syntax highlighting) style to use. 76 | pygments_style = 'sphinx' 77 | 78 | # -- Options for HTML output ------------------------------------------------- 79 | 80 | # The theme to use for HTML and HTML Help pages. See the documentation for 81 | # a list of builtin themes. 82 | # 83 | html_theme = 'sphinx_rtd_theme' 84 | 85 | # Add any paths that contain custom static files (such as style sheets) here, 86 | # relative to this directory. They are copied after the builtin static files, 87 | # so a file named "default.css" will overwrite the builtin "default.css". 88 | #html_static_path = ['_static'] 89 | 90 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 91 | html_show_copyright = False 92 | 93 | # Output file base name for HTML help builder. 94 | htmlhelp_basename = 'lcov_coberturadoc' 95 | 96 | # Grouping the document tree into LaTeX files. List of tuples 97 | # (source start file, target name, title, 98 | # author, documentclass [howto, manual, or own class]). 99 | latex_documents = [ 100 | (master_doc, 'lcov_cobertura.tex', 'lcov\\_cobertura Documentation', 101 | 'Peter Bennett', 'manual'), 102 | ] 103 | 104 | # One entry per manual page. List of tuples 105 | # (source start file, name, description, authors, manual section). 106 | man_pages = [ 107 | (master_doc, 'lcov_cobertura', 'lcov\\_cobertura Documentation', 108 | [author], 1) 109 | ] 110 | 111 | # Grouping the document tree into Texinfo files. List of tuples 112 | # (source start file, target name, title, author, 113 | # dir menu entry, description, category) 114 | texinfo_documents = [ 115 | (master_doc, 'lcov_cobertura', 'lcov\\_cobertura Documentation', 116 | author, 'lcov_cobertura', 'One line description of project.', 117 | 'Miscellaneous'), 118 | ] 119 | -------------------------------------------------------------------------------- /test/test_lcov_cobertura.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright 2011-2012 Eric Wendelin 4 | # 5 | # This is free software, licensed under the Apache License, Version 2.0, 6 | # available in the accompanying LICENSE.txt file. 7 | 8 | import unittest 9 | from xmldiff import main as xmldiff 10 | 11 | from lcov_cobertura import LcovCobertura 12 | 13 | 14 | class Test(unittest.TestCase): 15 | """Unit tests for lcov_cobertura.""" 16 | 17 | def test_parse(self): 18 | converter = LcovCobertura( 19 | 'SF:foo/file.ext\nDA:1,1\nDA:2,0\nBRDA:1,1,1,1\nBRDA:1,1,2,0\nend_of_record\n') 20 | result = converter.parse() 21 | self.assertTrue('packages' in result) 22 | self.assertTrue('foo' in result['packages']) 23 | self.assertEqual(result['packages']['foo']['branches-covered'], 1) 24 | self.assertEqual(result['packages']['foo']['branches-total'], 2) 25 | self.assertEqual(result['packages']['foo']['branch-rate'], '0.5') 26 | self.assertEqual(result['packages']['foo']['line-rate'], '0.5') 27 | self.assertEqual(result['packages']['foo']['lines-covered'], 1) 28 | self.assertEqual(result['packages']['foo']['lines-total'], 2) 29 | self.assertEqual(result['packages']['foo']['classes']['foo/file.ext']['branches-covered'], 1) 30 | self.assertEqual(result['packages']['foo']['classes']['foo/file.ext']['branches-total'], 2) 31 | self.assertEqual(result['packages']['foo']['classes']['foo/file.ext']['methods'], {}) 32 | 33 | def test_parse_with_functions(self): 34 | converter = LcovCobertura( 35 | 'TN:\nSF:foo/file.ext\nDA:1,1\nDA:2,0\nFN:1,(anonymous_1)\nFN:2,namedFn\nFNDA:1,(anonymous_1)\nend_of_record\n') 36 | result = converter.parse() 37 | self.assertEqual(result['packages']['foo']['line-rate'], '0.5') 38 | self.assertEqual(result['packages']['foo']['lines-covered'], 1) 39 | self.assertEqual(result['packages']['foo']['lines-total'], 2) 40 | self.assertEqual(result['packages']['foo']['classes']['foo/file.ext']['methods']['(anonymous_1)'], ['1', '1']) 41 | self.assertEqual(result['packages']['foo']['classes']['foo/file.ext']['methods']['namedFn'], ['2', '0']) 42 | 43 | def test_parse_with_checksum(self): 44 | converter = LcovCobertura( 45 | 'SF:foo/file.ext\nDA:1,1,dummychecksum\nDA:2,0,dummychecksum\nBRDA:1,1,1,1\nBRDA:1,1,2,0\nend_of_record\n') 46 | result = converter.parse() 47 | self.assertTrue('packages' in result) 48 | self.assertTrue('foo' in result['packages']) 49 | self.assertEqual(result['packages']['foo']['branches-covered'], 1) 50 | self.assertEqual(result['packages']['foo']['branches-total'], 2) 51 | self.assertEqual(result['packages']['foo']['branch-rate'], '0.5') 52 | self.assertEqual(result['packages']['foo']['line-rate'], '0.5') 53 | self.assertEqual(result['packages']['foo']['lines-covered'], 1) 54 | self.assertEqual(result['packages']['foo']['lines-total'], 2) 55 | self.assertEqual(result['packages']['foo']['classes']['foo/file.ext']['branches-covered'], 1) 56 | self.assertEqual(result['packages']['foo']['classes']['foo/file.ext']['branches-total'], 2) 57 | self.assertEqual(result['packages']['foo']['classes']['foo/file.ext']['methods'], {}) 58 | 59 | def test_exclude_package_from_parser(self): 60 | converter = LcovCobertura( 61 | 'SF:foo/file.ext\nDA:1,1\nDA:2,0\nend_of_record\nSF:bar/file.ext\nDA:1,1\nDA:2,1\nend_of_record\n', 62 | '.', 63 | 'foo') 64 | result = converter.parse() 65 | self.assertTrue('foo' not in result['packages']) 66 | self.assertTrue('bar' in result['packages']) 67 | # Verify that excluded package did not skew line coverage totals 68 | self.assertEqual(result['packages']['bar']['line-rate'], '1.0') 69 | 70 | def test_generate_cobertura_xml(self): 71 | converter = LcovCobertura( 72 | 'TN:\nSF:foo/file.ext\nDA:1,1\nDA:2,0\nBRDA:1,1,1,1\nBRDA:1,1,2,0\nFN:1,(anonymous_1)\nFN:2,namedFn\nFNDA:1,(anonymous_1)\nend_of_record\n') 73 | TEST_XML = r""" 74 | 76 | 77 | 78 | . 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | """ 106 | 107 | parsed_lcov = {'packages': { 108 | 'foo': {'branches-covered': 1, 'line-rate': '0.5', 'branch-rate': '0.5', 109 | 'lines-covered': 1, 'branches-total': 2, 'lines-total': 2, 110 | 'classes': { 111 | 'Bar': {'branches-covered': 1, 'lines-covered': 1, 112 | 'branches-total': 2, 113 | 'methods': { 114 | '(anonymous_1)': ['1', '1'], 115 | 'namedFn': ['2', '0'] 116 | }, 117 | 'lines': { 118 | 1: {'hits': '1', 'branches-covered': 1, 119 | 'branches-total': 2, 'branch': 'true'}, 120 | 2: {'hits': '0', 'branches-covered': 0, 121 | 'branches-total': 0, 'branch': 'false'} 122 | }, 123 | 'lines-total': 2, 'name': 'file.ext'}}, 124 | }}, 125 | 'summary': {'branches-covered': 1, 'branches-total': 2, 126 | 'lines-covered': 1, 'lines-total': 2}, 127 | 'timestamp': '1346815648000'} 128 | xml = converter.generate_cobertura_xml(parsed_lcov, indent=" ") 129 | xml_diff = xmldiff.diff_texts(xml, TEST_XML) 130 | self.assertEqual(len(xml_diff), 0) 131 | 132 | def test_treat_non_integer_line_execution_count_as_zero(self): 133 | converter = LcovCobertura( 134 | 'SF:foo/file.ext\nDA:1,=====\nDA:2,2\nBRDA:1,1,1,1\nBRDA:1,1,2,0\nend_of_record\n') 135 | result = converter.parse() 136 | self.assertEqual(result['packages']['foo']['lines-covered'], 1) 137 | self.assertEqual(result['packages']['foo']['lines-total'], 2) 138 | 139 | def test_support_function_names_with_commas(self): 140 | converter = LcovCobertura( 141 | 'TN:\nSF:foo/file.ext\nDA:1,1\nDA:2,0\nFN:1,(anonymous_1)\nFN:2,namedFn\nFNDA:1,(anonymous_1)\nend_of_record\n') 142 | result = converter.parse() 143 | self.assertEqual(result['packages']['foo']['classes']['foo/file.ext']['methods']['(anonymous_1)'], ['1', '1']) 144 | self.assertEqual(result['packages']['foo']['classes']['foo/file.ext']['methods']['namedFn'], ['2', '0']) 145 | 146 | def test_demangle(self): 147 | converter = LcovCobertura( 148 | "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", 149 | demangle=True) 150 | TEST_TIMESTAMP = 1594850794 151 | TEST_XML = r""" 152 | 154 | 155 | 156 | . 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | """.format(TEST_TIMESTAMP) 186 | result = converter.parse(timestamp=TEST_TIMESTAMP) 187 | xml = converter.generate_cobertura_xml(result, indent=" ") 188 | xml_diff = xmldiff.diff_texts(xml, TEST_XML) 189 | self.assertEqual(len(xml_diff), 0) 190 | 191 | if __name__ == '__main__': 192 | unittest.main(verbosity=2) 193 | -------------------------------------------------------------------------------- /.gitchangelog.rc: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8; mode: python -*- 2 | ## 3 | ## Message Format 4 | ## 5 | ## ACTION: [AUDIENCE:] COMMIT_MSG [!TAG ...] 6 | ## 7 | ## Description 8 | ## 9 | ## ACTION is one of 'chg', 'fix', 'new' 10 | ## 11 | ## Is WHAT the change is about. 12 | ## 13 | ## 'chg' is for refactor, small improvement, cosmetic changes... 14 | ## 'fix' is for bug fixes 15 | ## 'new' is for new features, big improvement 16 | ## 17 | ## AUDIENCE is optional and one of 'dev', 'usr', 'pkg', 'test', 'doc' 18 | ## 19 | ## Is WHO is concerned by the change. 20 | ## 21 | ## 'dev' is for developpers (API changes, refactors...) 22 | ## 'usr' is for final users (UI changes) 23 | ## 'pkg' is for packagers (packaging changes) 24 | ## 'test' is for testers (test only related changes) 25 | ## 'doc' is for doc guys (doc only changes) 26 | ## 27 | ## COMMIT_MSG is ... well ... the commit message itself. 28 | ## 29 | ## TAGs are additionnal adjective as 'refactor' 'minor' 'cosmetic' 30 | ## 31 | ## They are preceded with a '!' or a '@' (prefer the former, as the 32 | ## latter is wrongly interpreted in github.) Commonly used tags are: 33 | ## 34 | ## 'refactor' is obviously for refactoring code only 35 | ## 'minor' is for a very meaningless change (a typo, adding a comment) 36 | ## 'cosmetic' is for cosmetic driven change (re-indentation, 80-col...) 37 | ## 'wip' is for partial functionality but complete subfunctionality. 38 | ## 39 | ## Example: 40 | ## 41 | ## new: usr: support of bazaar implemented 42 | ## chg: re-indentend some lines !cosmetic 43 | ## new: dev: updated code to be compatible with last version of killer lib. 44 | ## fix: pkg: updated year of licence coverage. 45 | ## new: test: added a bunch of test around user usability of feature X. 46 | ## fix: typo in spelling my name in comment. !minor 47 | ## 48 | ## Please note that multi-line commit message are supported, and only the 49 | ## first line will be considered as the "summary" of the commit message. So 50 | ## tags, and other rules only applies to the summary. The body of the commit 51 | ## message will be displayed in the changelog without reformatting. 52 | 53 | 54 | ## 55 | ## ``ignore_regexps`` is a line of regexps 56 | ## 57 | ## Any commit having its full commit message matching any regexp listed here 58 | ## will be ignored and won't be reported in the changelog. 59 | ## 60 | #ignore_regexps = [] 61 | ignore_regexps = [ 62 | r'@minor', r'!minor', 63 | r'@cosmetic', r'!cosmetic', 64 | r'@refactor', r'!refactor', 65 | r'@wip', r'!wip', 66 | r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*[p|P]kg:', 67 | r'^(.{3,3}\s*:)?\s*[fF]irst commit.?\s*$', 68 | r'^$', ## ignore commits with empty messages 69 | ] 70 | 71 | 72 | ## ``section_regexps`` is a list of 2-tuples associating a string label and a 73 | ## list of regexp 74 | ## 75 | ## Commit messages will be classified in sections thanks to this. Section 76 | ## titles are the label, and a commit is classified under this section if any 77 | ## of the regexps associated is matching. 78 | ## 79 | ## Please note that ``section_regexps`` will only classify commits and won't 80 | ## make any changes to the contents. So you'll probably want to go check 81 | ## ``subject_process`` (or ``body_process``) to do some changes to the subject, 82 | ## whenever you are tweaking this variable. 83 | ## 84 | section_regexps = [ 85 | ('New', [ 86 | r'^[nN]ew\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$', 87 | ]), 88 | ('Features', [ 89 | r'^([nN]ew|[fF]eat)\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$', 90 | ]), 91 | ('Changes', [ 92 | r'^[cC]hg\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$', 93 | ]), 94 | ('Fixes', [ 95 | r'^[fF]ix\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$', 96 | ]), 97 | 98 | ('Other', None ## Match all lines 99 | ), 100 | ] 101 | 102 | 103 | ## ``body_process`` is a callable 104 | ## 105 | ## This callable will be given the original body and result will 106 | ## be used in the changelog. 107 | ## 108 | ## Available constructs are: 109 | ## 110 | ## - any python callable that take one txt argument and return txt argument. 111 | ## 112 | ## - ReSub(pattern, replacement): will apply regexp substitution. 113 | ## 114 | ## - Indent(chars=" "): will indent the text with the prefix 115 | ## Please remember that template engines gets also to modify the text and 116 | ## will usually indent themselves the text if needed. 117 | ## 118 | ## - Wrap(regexp=r"\n\n"): re-wrap text in separate paragraph to fill 80-Columns 119 | ## 120 | ## - noop: do nothing 121 | ## 122 | ## - ucfirst: ensure the first letter is uppercase. 123 | ## (usually used in the ``subject_process`` pipeline) 124 | ## 125 | ## - final_dot: ensure text finishes with a dot 126 | ## (usually used in the ``subject_process`` pipeline) 127 | ## 128 | ## - strip: remove any spaces before or after the content of the string 129 | ## 130 | ## - SetIfEmpty(msg="No commit message."): will set the text to 131 | ## whatever given ``msg`` if the current text is empty. 132 | ## 133 | ## Additionally, you can `pipe` the provided filters, for instance: 134 | #body_process = Wrap(regexp=r'\n(?=\w+\s*:)') | Indent(chars=" ") 135 | #body_process = Wrap(regexp=r'\n(?=\w+\s*:)') 136 | #body_process = noop 137 | body_process = ReSub(r'((^|\n)[A-Z]\w+(-\w+)*: .*(\n\s+.*)*)+$', r'') | strip 138 | #body_process = lambda text: "" 139 | #body_process = ReSub(r'.*', '') 140 | 141 | 142 | ## ``subject_process`` is a callable 143 | ## 144 | ## This callable will be given the original subject and result will 145 | ## be used in the changelog. 146 | ## 147 | ## Available constructs are those listed in ``body_process`` doc. 148 | subject_process = (strip | 149 | ReSub(r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n@]*)(@[a-z]+\s+)*$', r'\4') | 150 | SetIfEmpty("No commit message.") | ucfirst | final_dot) 151 | 152 | 153 | ## ``tag_filter_regexp`` is a regexp 154 | ## 155 | ## Tags that will be used for the changelog must match this regexp. 156 | ## 157 | #tag_filter_regexp = r'^v?[0-9]+\.[0-9]+(\.[0-9]+)?$' 158 | tag_filter_regexp = r"^.*$" 159 | 160 | ## ``unreleased_version_label`` is a string or a callable that outputs a string 161 | ## 162 | ## This label will be used as the changelog Title of the last set of changes 163 | ## between last valid tag and HEAD if any. 164 | # custom template (.tpl file below) overrides this setting 165 | unreleased_version_label = lambda: swrap( 166 | ["git", "describe", "--tags"], 167 | shell=False) 168 | #unreleased_version_label = "(unreleased)" 169 | 170 | 171 | ## ``output_engine`` is a callable 172 | ## 173 | ## This will change the output format of the generated changelog file 174 | ## 175 | ## Available choices are: 176 | ## 177 | ## - rest_py 178 | ## 179 | ## Legacy pure python engine, outputs ReSTructured text. 180 | ## This is the default. 181 | ## 182 | ## - mustache() 183 | ## 184 | ## Template name could be any of the available templates in 185 | ## ``templates/mustache/*.tpl``. 186 | ## Requires python package ``pystache``. 187 | ## Examples: 188 | ## - mustache("markdown") 189 | ## - mustache("restructuredtext") 190 | ## 191 | ## - makotemplate() 192 | ## 193 | ## Template name could be any of the available templates in 194 | ## ``templates/mako/*.tpl``. 195 | ## Requires python package ``mako``. 196 | ## Examples: 197 | ## - makotemplate("restructuredtext") 198 | ## 199 | #output_engine = rest_py 200 | #output_engine = mustache("restructuredtext") 201 | output_engine = mustache(".gitchangelog-keepachangelog.tpl") 202 | #output_engine = mustache("markdown") 203 | #output_engine = makotemplate("restructuredtext") 204 | 205 | 206 | ## ``include_merge`` is a boolean 207 | ## 208 | ## This option tells git-log whether to include merge commits in the log. 209 | ## The default is to include them. 210 | include_merge = False 211 | 212 | 213 | ## ``log_encoding`` is a string identifier 214 | ## 215 | ## This option tells gitchangelog what encoding is outputed by ``git log``. 216 | ## The default is to be clever about it: it checks ``git config`` for 217 | ## ``i18n.logOutputEncoding``, and if not found will default to git's own 218 | ## default: ``utf-8``. 219 | #log_encoding = 'utf-8' 220 | 221 | 222 | ## ``publish`` is a callable 223 | ## 224 | ## Sets what ``gitchangelog`` should do with the output generated by 225 | ## the output engine. ``publish`` is a callable taking one argument 226 | ## that is an interator on lines from the output engine. 227 | ## 228 | ## Some helper callable are provided: 229 | ## 230 | ## Available choices are: 231 | ## 232 | ## - stdout 233 | ## 234 | ## Outputs directly to standard output 235 | ## (This is the default) 236 | ## 237 | ## - FileInsertAtFirstRegexMatch(file, pattern, idx=lamda m: m.start(), flags) 238 | ## 239 | ## Creates a callable that will parse given file for the given 240 | ## regex pattern and will insert the output in the file. 241 | ## ``idx`` is a callable that receive the matching object and 242 | ## must return a integer index point where to insert the 243 | ## the output in the file. Default is to return the position of 244 | ## the start of the matched string. 245 | ## 246 | ## - FileRegexSubst(file, pattern, replace, flags) 247 | ## 248 | ## Apply a replace inplace in the given file. Your regex pattern must 249 | ## take care of everything and might be more complex. Check the README 250 | ## for a complete copy-pastable example. 251 | ## 252 | # publish = FileInsertIntoFirstRegexMatch( 253 | # "CHANGELOG.rst", 254 | # r'/(?P[0-9]+\.[0-9]+(\.[0-9]+)?)\s+\([0-9]+-[0-9]{2}-[0-9]{2}\)\n--+\n/', 255 | # idx=lambda m: m.start(1) 256 | # ) 257 | 258 | publish = stdout 259 | 260 | 261 | ## ``revs`` is a list of callable or a list of string 262 | ## 263 | ## callable will be called to resolve as strings and allow dynamical 264 | ## computation of these. The result will be used as revisions for 265 | ## gitchangelog (as if directly stated on the command line). This allows 266 | ## to filter exaclty which commits will be read by gitchangelog. 267 | ## 268 | ## To get a full documentation on the format of these strings, please 269 | ## refer to the ``git rev-list`` arguments. There are many examples. 270 | ## 271 | ## Using callables is especially useful, for instance, if you 272 | ## are using gitchangelog to generate incrementally your changelog. 273 | ## 274 | ## Some helpers are provided, you can use them:: 275 | ## 276 | ## - FileFirstRegexMatch(file, pattern): will return a callable that will 277 | ## return the first string match for the given pattern in the given file. 278 | ## If you use named sub-patterns in your regex pattern, it'll output only 279 | ## the string matching the regex pattern named "rev". 280 | ## 281 | ## - Caret(rev): will return the rev prefixed by a "^", which is a 282 | ## way to remove the given revision and all its ancestor. 283 | ## 284 | ## Please note that if you provide a rev-list on the command line, it'll 285 | ## replace this value (which will then be ignored). 286 | ## 287 | ## If empty, then ``gitchangelog`` will act as it had to generate a full 288 | ## changelog. 289 | ## 290 | ## The default is to use all commits to make the changelog. 291 | #revs = ["^1.0.3", ] 292 | #revs = [ 293 | # Caret( 294 | # FileFirstRegexMatch( 295 | # "CHANGELOG.rst", 296 | # r"(?P[0-9]+\.[0-9]+(\.[0-9]+)?)\s+\([0-9]+-[0-9]{2}-[0-9]{2}\)\n--+\n")), 297 | # "HEAD" 298 | #] 299 | revs = [] 300 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /lcov_cobertura/lcov_cobertura.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright 2011-2022 Eric Wendelin 4 | # 5 | # This is free software, licensed under the Apache License, Version 2.0, 6 | # available in the accompanying LICENSE.txt file. 7 | 8 | """ 9 | Converts lcov line coverage output to Cobertura-compatible XML for CI 10 | """ 11 | 12 | import re 13 | import sys 14 | import os 15 | import time 16 | import subprocess # nosec - not for untrusted input 17 | 18 | from xml.dom import minidom # nosec - not for untrusted input 19 | from optparse import OptionParser 20 | 21 | from shutil import which 22 | 23 | if sys.version_info < (3, 8): 24 | from importlib_metadata import version 25 | else: 26 | from importlib.metadata import version 27 | 28 | __version__ = version('lcov_cobertura') 29 | 30 | CPPFILT = "c++filt" 31 | HAVE_CPPFILT = False 32 | 33 | if which(CPPFILT) is not None: 34 | HAVE_CPPFILT = True 35 | 36 | 37 | class Demangler(): 38 | def __init__(self): 39 | self.pipe = subprocess.Popen( # nosec - not for untrusted input 40 | [CPPFILT], stdin=subprocess.PIPE, stdout=subprocess.PIPE) 41 | 42 | def demangle(self, name): 43 | newname = name + "\n" 44 | self.pipe.stdin.write(newname.encode('utf-8')) 45 | self.pipe.stdin.flush() 46 | res = self.pipe.stdout.readline().decode('utf-8') 47 | return res.rstrip() 48 | 49 | def __del__(self): 50 | self.pipe.stdin.close() 51 | self.pipe.terminate() 52 | self.pipe.wait() 53 | 54 | 55 | class LcovCobertura(): 56 | """ 57 | Converts code coverage report files in lcov format to Cobertura's XML 58 | report format so that CI servers like Jenkins can aggregate results and 59 | determine build stability etc. 60 | 61 | >>> from lcov_cobertura import LcovCobertura 62 | >>> LCOV_INPUT = 'your lcov input' 63 | >>> converter = LcovCobertura(LCOV_INPUT) 64 | >>> cobertura_xml = converter.convert() 65 | >>> print(cobertura_xml) 66 | 67 | 69 | ... 70 | """ 71 | 72 | def __init__(self, lcov_data, base_dir='.', excludes=None, demangle=False): 73 | """ 74 | Create a new :class:`LcovCobertura` object using the given `lcov_data` 75 | and `options`. 76 | 77 | :param lcov_data: Path to LCOV data file 78 | :type lcov_data: string 79 | :param base_dir: Path upon which to base all sources 80 | :type base_dir: string 81 | :param excludes: list of regexes to packages as excluded 82 | :type excludes: [string] 83 | :param demangle: whether to demangle function names using c++filt 84 | :type demangle: bool 85 | """ 86 | 87 | if not excludes: 88 | excludes = [] 89 | self.lcov_data = lcov_data 90 | self.base_dir = base_dir 91 | self.excludes = excludes 92 | if demangle: 93 | demangler = Demangler() 94 | self.format = demangler.demangle 95 | else: 96 | self.format = lambda x: x 97 | 98 | def convert(self): 99 | """ 100 | Convert lcov file to cobertura XML using options from this instance. 101 | """ 102 | coverage_data = self.parse() 103 | return self.generate_cobertura_xml(coverage_data) 104 | 105 | def parse(self, **kwargs): 106 | """ 107 | Generate a data structure representing it that can be serialized in any 108 | logical format. 109 | """ 110 | 111 | coverage_data = { 112 | 'packages': {}, 113 | 'summary': {'lines-total': 0, 'lines-covered': 0, 114 | 'branches-total': 0, 'branches-covered': 0}, 115 | 'timestamp': str(kwargs["timestamp"]) if "timestamp" in kwargs else str(int(time.time())) 116 | } 117 | package = None 118 | current_file = None 119 | file_lines_total = 0 120 | file_lines_covered = 0 121 | file_lines = {} 122 | file_methods = {} 123 | file_branches_total = 0 124 | file_branches_covered = 0 125 | 126 | for line in self.lcov_data.split('\n'): 127 | if line.strip() == 'end_of_record': 128 | if current_file is not None: 129 | package_dict = coverage_data['packages'][package] 130 | package_dict['lines-total'] += file_lines_total 131 | package_dict['lines-covered'] += file_lines_covered 132 | package_dict['branches-total'] += file_branches_total 133 | package_dict['branches-covered'] += file_branches_covered 134 | file_dict = package_dict['classes'][current_file] 135 | file_dict['lines-total'] = file_lines_total 136 | file_dict['lines-covered'] = file_lines_covered 137 | file_dict['lines'] = dict(file_lines) 138 | file_dict['methods'] = dict(file_methods) 139 | file_dict['branches-total'] = file_branches_total 140 | file_dict['branches-covered'] = file_branches_covered 141 | coverage_data['summary']['lines-total'] += file_lines_total 142 | coverage_data['summary']['lines-covered'] += file_lines_covered 143 | coverage_data['summary']['branches-total'] += file_branches_total 144 | coverage_data['summary']['branches-covered'] += file_branches_covered 145 | 146 | line_parts = line.split(':', 1) 147 | input_type = line_parts[0] 148 | 149 | if input_type == 'SF': 150 | # Get file name 151 | file_name = line_parts[-1].strip() 152 | relative_file_name = os.path.relpath(file_name, self.base_dir) 153 | package = '.'.join(relative_file_name.split(os.path.sep)[0:-1]) 154 | class_name = '.'.join(relative_file_name.split(os.path.sep)) 155 | if package not in coverage_data['packages']: 156 | coverage_data['packages'][package] = { 157 | 'classes': {}, 'lines-total': 0, 'lines-covered': 0, 158 | 'branches-total': 0, 'branches-covered': 0 159 | } 160 | coverage_data['packages'][package]['classes'][ 161 | relative_file_name] = { 162 | 'name': class_name, 'lines': {}, 'lines-total': 0, 163 | 'lines-covered': 0, 'branches-total': 0, 164 | 'branches-covered': 0 165 | } 166 | package = package 167 | current_file = relative_file_name 168 | file_lines_total = 0 169 | file_lines_covered = 0 170 | file_lines.clear() 171 | file_methods.clear() 172 | file_branches_total = 0 173 | file_branches_covered = 0 174 | elif input_type == 'DA': 175 | # DA:2,0 176 | (line_number, line_hits) = line_parts[-1].strip().split(',')[:2] 177 | line_number = int(line_number) 178 | if line_number not in file_lines: 179 | file_lines[line_number] = { 180 | 'branch': 'false', 'branches-total': 0, 181 | 'branches-covered': 0 182 | } 183 | file_lines[line_number]['hits'] = line_hits 184 | # Increment lines total/covered for class and package 185 | try: 186 | if int(line_hits) > 0: 187 | file_lines_covered += 1 188 | except ValueError: 189 | pass 190 | file_lines_total += 1 191 | elif input_type == 'BRDA': 192 | # BRDA:1,1,2,0 193 | (line_number, block_number, branch_number, branch_hits) = line_parts[-1].strip().split(',') 194 | line_number = int(line_number) 195 | if line_number not in file_lines: 196 | file_lines[line_number] = { 197 | 'branch': 'true', 'branches-total': 0, 198 | 'branches-covered': 0, 'hits': 0 199 | } 200 | file_lines[line_number]['branch'] = 'true' 201 | file_lines[line_number]['branches-total'] += 1 202 | file_branches_total += 1 203 | if branch_hits != '-' and int(branch_hits) > 0: 204 | file_lines[line_number]['branches-covered'] += 1 205 | file_branches_covered += 1 206 | elif input_type == 'BRF': 207 | file_branches_total = int(line_parts[1]) 208 | elif input_type == 'BRH': 209 | file_branches_covered = int(line_parts[1]) 210 | elif input_type == 'FN': 211 | # FN:5,(anonymous_1) 212 | function_line, function_name = line_parts[-1].strip().split(',', 1) 213 | file_methods[function_name] = [function_line, '0'] 214 | elif input_type == 'FNDA': 215 | # FNDA:0,(anonymous_1) 216 | (function_hits, function_name) = line_parts[-1].strip().split(',', 1) 217 | if function_name not in file_methods: 218 | file_methods[function_name] = ['0', '0'] 219 | file_methods[function_name][-1] = function_hits 220 | 221 | # Exclude packages 222 | excluded = [x for x in coverage_data['packages'] for e in self.excludes 223 | if re.match(e, x)] 224 | for package in excluded: 225 | del coverage_data['packages'][package] 226 | 227 | # Compute line coverage rates 228 | for package_data in list(coverage_data['packages'].values()): 229 | package_data['line-rate'] = self._percent( 230 | package_data['lines-total'], 231 | package_data['lines-covered']) 232 | package_data['branch-rate'] = self._percent( 233 | package_data['branches-total'], 234 | package_data['branches-covered']) 235 | 236 | return coverage_data 237 | 238 | def generate_cobertura_xml(self, coverage_data, **kwargs): 239 | """ 240 | Given parsed coverage data, return a String cobertura XML representation. 241 | 242 | :param coverage_data: Nested dict representing coverage information. 243 | :type coverage_data: dict 244 | """ 245 | 246 | dom_impl = minidom.getDOMImplementation() 247 | doctype = dom_impl.createDocumentType("coverage", None, 248 | "http://cobertura.sourceforge.net/xml/coverage-04.dtd") 249 | document = dom_impl.createDocument(None, "coverage", doctype) 250 | root = document.documentElement 251 | summary = coverage_data['summary'] 252 | self._attrs(root, { 253 | 'branch-rate': self._percent(summary['branches-total'], 254 | summary['branches-covered']), 255 | 'branches-covered': str(summary['branches-covered']), 256 | 'branches-valid': str(summary['branches-total']), 257 | 'complexity': '0', 258 | 'line-rate': self._percent(summary['lines-total'], 259 | summary['lines-covered']), 260 | 'lines-covered': str(summary['lines-covered']), 261 | 'lines-valid': str(summary['lines-total']), 262 | 'timestamp': coverage_data['timestamp'], 263 | 'version': '2.0.3', 264 | }) 265 | 266 | sources = self._el(document, 'sources', {}) 267 | source = self._el(document, 'source', {}) 268 | source.appendChild(document.createTextNode(self.base_dir)) 269 | sources.appendChild(source) 270 | 271 | root.appendChild(sources) 272 | 273 | packages_el = self._el(document, 'packages', {}) 274 | 275 | packages = coverage_data['packages'] 276 | for package_name, package_data in list(packages.items()): 277 | package_el = self._el(document, 'package', { 278 | 'line-rate': package_data['line-rate'], 279 | 'branch-rate': package_data['branch-rate'], 280 | 'name': package_name, 281 | 'complexity': '0', 282 | }) 283 | classes_el = self._el(document, 'classes', {}) 284 | for class_name, class_data in list(package_data['classes'].items()): 285 | class_el = self._el(document, 'class', { 286 | 'branch-rate': self._percent(class_data['branches-total'], 287 | class_data['branches-covered']), 288 | 'complexity': '0', 289 | 'filename': class_name, 290 | 'line-rate': self._percent(class_data['lines-total'], 291 | class_data['lines-covered']), 292 | 'name': class_data['name'] 293 | }) 294 | 295 | # Process methods 296 | methods_el = self._el(document, 'methods', {}) 297 | for method_name, (line, hits) in list(class_data['methods'].items()): 298 | method_el = self._el(document, 'method', { 299 | 'name': self.format(method_name), 300 | 'signature': '', 301 | 'line-rate': '1.0' if int(hits) > 0 else '0.0', 302 | 'branch-rate': '1.0' if int(hits) > 0 else '0.0', 303 | }) 304 | method_lines_el = self._el(document, 'lines', {}) 305 | method_line_el = self._el(document, 'line', { 306 | 'hits': hits, 307 | 'number': line, 308 | 'branch': 'false', 309 | }) 310 | method_lines_el.appendChild(method_line_el) 311 | method_el.appendChild(method_lines_el) 312 | methods_el.appendChild(method_el) 313 | 314 | # Process lines 315 | lines_el = self._el(document, 'lines', {}) 316 | lines = list(class_data['lines'].keys()) 317 | lines.sort() 318 | for line_number in lines: 319 | line_el = self._el(document, 'line', { 320 | 'branch': class_data['lines'][line_number]['branch'], 321 | 'hits': str(class_data['lines'][line_number]['hits']), 322 | 'number': str(line_number) 323 | }) 324 | if class_data['lines'][line_number]['branch'] == 'true': 325 | total = int(class_data['lines'][line_number]['branches-total']) 326 | covered = int(class_data['lines'][line_number]['branches-covered']) 327 | percentage = int((covered * 100.0) / total) 328 | line_el.setAttribute('condition-coverage', 329 | '{0}% ({1}/{2})'.format( 330 | percentage, covered, total)) 331 | lines_el.appendChild(line_el) 332 | 333 | class_el.appendChild(methods_el) 334 | class_el.appendChild(lines_el) 335 | classes_el.appendChild(class_el) 336 | package_el.appendChild(classes_el) 337 | packages_el.appendChild(package_el) 338 | root.appendChild(packages_el) 339 | 340 | return document.toprettyxml(**kwargs) 341 | 342 | def _el(self, document, name, attrs): 343 | """ 344 | Create an element within document with given name and attributes. 345 | 346 | :param document: Document element 347 | :type document: Document 348 | :param name: Element name 349 | :type name: string 350 | :param attrs: Attributes for element 351 | :type attrs: dict 352 | """ 353 | return self._attrs(document.createElement(name), attrs) 354 | 355 | def _attrs(self, element, attrs): 356 | """ 357 | Set attributes on given element. 358 | 359 | :param element: DOM Element 360 | :type element: Element 361 | :param attrs: Attributes for element 362 | :type attrs: dict 363 | """ 364 | for attr, val in list(attrs.items()): 365 | element.setAttribute(attr, val) 366 | return element 367 | 368 | def _percent(self, lines_total, lines_covered): 369 | """ 370 | Get the percentage of lines covered in the total, with formatting. 371 | 372 | :param lines_total: Total number of lines in given module 373 | :type lines_total: number 374 | :param lines_covered: Number of lines covered by tests in module 375 | :type lines_covered: number 376 | """ 377 | 378 | if lines_total == 0: 379 | return '0.0' 380 | return str(float(float(lines_covered) / float(lines_total))) 381 | 382 | 383 | def main(argv=None): 384 | """ 385 | Converts LCOV coverage data to Cobertura-compatible XML for reporting. 386 | 387 | Usage: 388 | lcov_cobertura.py lcov-file.dat 389 | lcov_cobertura.py lcov-file.dat -b src/dir -e test.lib -o path/out.xml 390 | 391 | By default, XML output will be written to ./coverage.xml 392 | """ 393 | if argv is None: 394 | argv = sys.argv 395 | parser = OptionParser() 396 | parser.usage = ('lcov_cobertura.py lcov-file.dat [-b source/dir] ' 397 | '[-e ] [-o output.xml] [-d]') 398 | parser.description = 'Converts lcov output to cobertura-compatible XML' 399 | parser.add_option('-b', '--base-dir', action='store', 400 | help='Directory where source files are located', 401 | dest='base_dir', default='.') 402 | parser.add_option('-e', '--excludes', 403 | help='Comma-separated list of regexes of packages to exclude', 404 | action='append', dest='excludes', default=[]) 405 | parser.add_option('-o', '--output', 406 | help='Path to store cobertura xml file', 407 | action='store', dest='output', default='coverage.xml') 408 | parser.add_option('-d', '--demangle', 409 | help='Demangle C++ function names using %s' % CPPFILT, 410 | action='store_true', dest='demangle', default=False) 411 | parser.add_option('-v', '--version', 412 | help='Display version info', 413 | action='store_true') 414 | (options, args) = parser.parse_args(args=argv) 415 | 416 | if options.demangle and not HAVE_CPPFILT: 417 | raise RuntimeError("C++ filter executable (%s) not found!" % CPPFILT) 418 | if options.version: 419 | print('[lcov_cobertura {}]'.format(__version__)) 420 | sys.exit(0) 421 | 422 | if len(args) != 2: 423 | print(main.__doc__) 424 | sys.exit(1) 425 | 426 | try: 427 | with open(args[1], 'r') as lcov_file: 428 | lcov_data = lcov_file.read() 429 | lcov_cobertura = LcovCobertura(lcov_data, options.base_dir, options.excludes, options.demangle) 430 | cobertura_xml = lcov_cobertura.convert() 431 | with open(options.output, mode='wt') as output_file: 432 | output_file.write(cobertura_xml) 433 | except IOError: 434 | sys.stderr.write("Unable to convert %s to Cobertura XML" % args[1]) 435 | 436 | 437 | if __name__ == '__main__': 438 | main() 439 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # A comma-separated list of package or module names from where C extensions may 4 | # be loaded. Extensions are loading into the active Python interpreter and may 5 | # run arbitrary code. 6 | extension-pkg-allow-list= 7 | 8 | # A comma-separated list of package or module names from where C extensions may 9 | # be loaded. Extensions are loading into the active Python interpreter and may 10 | # run arbitrary code. (This is an alternative name to extension-pkg-allow-list 11 | # for backward compatibility.) 12 | extension-pkg-whitelist= 13 | 14 | # Return non-zero exit code if any of these messages/categories are detected, 15 | # even if score is above --fail-under value. Syntax same as enable. Messages 16 | # specified are enabled, while categories only check already-enabled messages. 17 | fail-on= 18 | 19 | # Specify a score threshold to be exceeded before program exits with error. 20 | fail-under=10.0 21 | 22 | # Files or directories to be skipped. They should be base names, not paths. 23 | ignore=CVS 24 | 25 | # Add files or directories matching the regex patterns to the ignore-list. The 26 | # regex matches against paths. 27 | ignore-paths= 28 | 29 | # Files or directories matching the regex patterns are skipped. The regex 30 | # matches against base names, not paths. 31 | ignore-patterns= 32 | 33 | # Python code to execute, usually for sys.path manipulation such as 34 | # pygtk.require(). 35 | #init-hook= 36 | 37 | # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the 38 | # number of processors available to use. 39 | jobs=1 40 | 41 | # Control the amount of potential inferred values when inferring a single 42 | # object. This can help the performance when dealing with large functions or 43 | # complex, nested conditions. 44 | limit-inference-results=100 45 | 46 | # List of plugins (as comma separated values of python module names) to load, 47 | # usually to register additional checkers. 48 | load-plugins= 49 | 50 | # Pickle collected data for later comparisons. 51 | persistent=yes 52 | 53 | # When enabled, pylint would attempt to guess common misconfiguration and emit 54 | # user-friendly hints instead of false-positive error messages. 55 | suggestion-mode=yes 56 | 57 | # Allow loading of arbitrary C extensions. Extensions are imported into the 58 | # active Python interpreter and may run arbitrary code. 59 | unsafe-load-any-extension=no 60 | 61 | 62 | [MESSAGES CONTROL] 63 | 64 | # Only show warnings with the listed confidence levels. Leave empty to show 65 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. 66 | confidence= 67 | 68 | # Disable the message, report, category or checker with the given id(s). You 69 | # can either give multiple identifiers separated by comma (,) or put this 70 | # option multiple times (only on the command line, not in the configuration 71 | # file where it should appear only once). You can also use "--disable=all" to 72 | # disable everything first and then reenable specific checks. For example, if 73 | # you want to run only the similarities checker, you can use "--disable=all 74 | # --enable=similarities". If you want to run only the classes checker, but have 75 | # no Warning level messages displayed, use "--disable=all --enable=classes 76 | # --disable=W". 77 | disable=print-statement, 78 | parameter-unpacking, 79 | unpacking-in-except, 80 | old-raise-syntax, 81 | backtick, 82 | long-suffix, 83 | old-ne-operator, 84 | old-octal-literal, 85 | import-star-module-level, 86 | non-ascii-bytes-literal, 87 | raw-checker-failed, 88 | bad-inline-option, 89 | locally-disabled, 90 | file-ignored, 91 | suppressed-message, 92 | useless-suppression, 93 | deprecated-pragma, 94 | use-symbolic-message-instead, 95 | apply-builtin, 96 | basestring-builtin, 97 | buffer-builtin, 98 | cmp-builtin, 99 | coerce-builtin, 100 | execfile-builtin, 101 | file-builtin, 102 | long-builtin, 103 | raw_input-builtin, 104 | reduce-builtin, 105 | standarderror-builtin, 106 | unicode-builtin, 107 | xrange-builtin, 108 | coerce-method, 109 | delslice-method, 110 | getslice-method, 111 | setslice-method, 112 | no-absolute-import, 113 | old-division, 114 | dict-iter-method, 115 | dict-view-method, 116 | next-method-called, 117 | metaclass-assignment, 118 | indexing-exception, 119 | raising-string, 120 | reload-builtin, 121 | oct-method, 122 | hex-method, 123 | nonzero-method, 124 | cmp-method, 125 | input-builtin, 126 | round-builtin, 127 | intern-builtin, 128 | unichr-builtin, 129 | map-builtin-not-iterating, 130 | zip-builtin-not-iterating, 131 | range-builtin-not-iterating, 132 | filter-builtin-not-iterating, 133 | using-cmp-argument, 134 | eq-without-hash, 135 | div-method, 136 | idiv-method, 137 | rdiv-method, 138 | exception-message-attribute, 139 | invalid-str-codec, 140 | sys-max-int, 141 | bad-python3-import, 142 | deprecated-string-function, 143 | deprecated-str-translate-call, 144 | deprecated-itertools-function, 145 | deprecated-types-field, 146 | next-method-defined, 147 | dict-items-not-iterating, 148 | dict-keys-not-iterating, 149 | dict-values-not-iterating, 150 | deprecated-operator-function, 151 | deprecated-urllib-function, 152 | xreadlines-attribute, 153 | deprecated-sys-function, 154 | exception-escape, 155 | comprehension-escape 156 | 157 | # Enable the message, report, category or checker with the given id(s). You can 158 | # either give multiple identifier separated by comma (,) or put this option 159 | # multiple time (only on the command line, not in the configuration file where 160 | # it should appear only once). See also the "--disable" option for examples. 161 | enable=c-extension-no-member 162 | 163 | 164 | [REPORTS] 165 | 166 | # Python expression which should return a score less than or equal to 10. You 167 | # have access to the variables 'error', 'warning', 'refactor', and 'convention' 168 | # which contain the number of messages in each category, as well as 'statement' 169 | # which is the total number of statements analyzed. This score is used by the 170 | # global evaluation report (RP0004). 171 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 172 | 173 | # Template used to display messages. This is a python new-style format string 174 | # used to format the message information. See doc for all details. 175 | #msg-template= 176 | 177 | # Set the output format. Available formats are text, parseable, colorized, json 178 | # and msvs (visual studio). You can also give a reporter class, e.g. 179 | # mypackage.mymodule.MyReporterClass. 180 | output-format=text 181 | 182 | # Tells whether to display a full report or only the messages. 183 | reports=no 184 | 185 | # Activate the evaluation score. 186 | score=yes 187 | 188 | 189 | [REFACTORING] 190 | 191 | # Maximum number of nested blocks for function / method body 192 | max-nested-blocks=5 193 | 194 | # Complete name of functions that never returns. When checking for 195 | # inconsistent-return-statements if a never returning function is called then 196 | # it will be considered as an explicit return statement and no message will be 197 | # printed. 198 | never-returning-functions=sys.exit,argparse.parse_error 199 | 200 | 201 | [VARIABLES] 202 | 203 | # List of additional names supposed to be defined in builtins. Remember that 204 | # you should avoid defining new builtins when possible. 205 | additional-builtins= 206 | 207 | # Tells whether unused global variables should be treated as a violation. 208 | allow-global-unused-variables=yes 209 | 210 | # List of names allowed to shadow builtins 211 | allowed-redefined-builtins= 212 | 213 | # List of strings which can identify a callback function by name. A callback 214 | # name must start or end with one of those strings. 215 | callbacks=cb_, 216 | _cb 217 | 218 | # A regular expression matching the name of dummy variables (i.e. expected to 219 | # not be used). 220 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ 221 | 222 | # Argument names that match this expression will be ignored. Default to name 223 | # with leading underscore. 224 | ignored-argument-names=_.*|^ignored_|^unused_ 225 | 226 | # Tells whether we should check for unused import in __init__ files. 227 | init-import=no 228 | 229 | # List of qualified module names which can have objects that can redefine 230 | # builtins. 231 | redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io 232 | 233 | 234 | [TYPECHECK] 235 | 236 | # List of decorators that produce context managers, such as 237 | # contextlib.contextmanager. Add to this list to register other decorators that 238 | # produce valid context managers. 239 | contextmanager-decorators=contextlib.contextmanager 240 | 241 | # List of members which are set dynamically and missed by pylint inference 242 | # system, and so shouldn't trigger E1101 when accessed. Python regular 243 | # expressions are accepted. 244 | generated-members= 245 | 246 | # Tells whether missing members accessed in mixin class should be ignored. A 247 | # mixin class is detected if its name ends with "mixin" (case insensitive). 248 | ignore-mixin-members=yes 249 | 250 | # Tells whether to warn about missing members when the owner of the attribute 251 | # is inferred to be None. 252 | ignore-none=yes 253 | 254 | # This flag controls whether pylint should warn about no-member and similar 255 | # checks whenever an opaque object is returned when inferring. The inference 256 | # can return multiple potential results while evaluating a Python object, but 257 | # some branches might not be evaluated, which results in partial inference. In 258 | # that case, it might be useful to still emit no-member and other checks for 259 | # the rest of the inferred objects. 260 | ignore-on-opaque-inference=yes 261 | 262 | # List of class names for which member attributes should not be checked (useful 263 | # for classes with dynamically set attributes). This supports the use of 264 | # qualified names. 265 | ignored-classes=optparse.Values,thread._local,_thread._local 266 | 267 | # List of module names for which member attributes should not be checked 268 | # (useful for modules/projects where namespaces are manipulated during runtime 269 | # and thus existing member attributes cannot be deduced by static analysis). It 270 | # supports qualified module names, as well as Unix pattern matching. 271 | ignored-modules= 272 | 273 | # Show a hint with possible names when a member name was not found. The aspect 274 | # of finding the hint is based on edit distance. 275 | missing-member-hint=yes 276 | 277 | # The minimum edit distance a name should have in order to be considered a 278 | # similar match for a missing member name. 279 | missing-member-hint-distance=1 280 | 281 | # The total number of similar names that should be taken in consideration when 282 | # showing a hint for a missing member. 283 | missing-member-max-choices=1 284 | 285 | # List of decorators that change the signature of a decorated function. 286 | signature-mutators= 287 | 288 | 289 | [STRING] 290 | 291 | # This flag controls whether inconsistent-quotes generates a warning when the 292 | # character used as a quote delimiter is used inconsistently within a module. 293 | check-quote-consistency=no 294 | 295 | # This flag controls whether the implicit-str-concat should generate a warning 296 | # on implicit string concatenation in sequences defined over several lines. 297 | check-str-concat-over-line-jumps=no 298 | 299 | 300 | [LOGGING] 301 | 302 | # The type of string formatting that logging methods do. `old` means using % 303 | # formatting, `new` is for `{}` formatting. 304 | logging-format-style=old 305 | 306 | # Logging modules to check that the string format arguments are in logging 307 | # function parameter format. 308 | logging-modules=logging 309 | 310 | 311 | [SIMILARITIES] 312 | 313 | # Comments are removed from the similarity computation 314 | ignore-comments=yes 315 | 316 | # Docstrings are removed from the similarity computation 317 | ignore-docstrings=yes 318 | 319 | # Imports are removed from the similarity computation 320 | ignore-imports=no 321 | 322 | # Signatures are removed from the similarity computation 323 | ignore-signatures=no 324 | 325 | # Minimum lines number of a similarity. 326 | min-similarity-lines=4 327 | 328 | 329 | [FORMAT] 330 | 331 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 332 | expected-line-ending-format= 333 | 334 | # Regexp for a line that is allowed to be longer than the limit. 335 | ignore-long-lines=^\s*(# )??$ 336 | 337 | # Number of spaces of indent required inside a hanging or continued line. 338 | indent-after-paren=4 339 | 340 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 341 | # tab). 342 | indent-string=' ' 343 | 344 | # Maximum number of characters on a single line. 345 | max-line-length=107 346 | 347 | # Maximum number of lines in a module. 348 | max-module-lines=1000 349 | 350 | # Allow the body of a class to be on the same line as the declaration if body 351 | # contains single statement. 352 | single-line-class-stmt=no 353 | 354 | # Allow the body of an if to be on the same line as the test if there is no 355 | # else. 356 | single-line-if-stmt=no 357 | 358 | 359 | [SPELLING] 360 | 361 | # Limits count of emitted suggestions for spelling mistakes. 362 | max-spelling-suggestions=4 363 | 364 | # Spelling dictionary name. Available dictionaries: none. To make it work, 365 | # install the 'python-enchant' package. 366 | spelling-dict= 367 | 368 | # List of comma separated words that should be considered directives if they 369 | # appear and the beginning of a comment and should not be checked. 370 | spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: 371 | 372 | # List of comma separated words that should not be checked. 373 | spelling-ignore-words= 374 | 375 | # A path to a file that contains the private dictionary; one word per line. 376 | spelling-private-dict-file= 377 | 378 | # Tells whether to store unknown words to the private dictionary (see the 379 | # --spelling-private-dict-file option) instead of raising a message. 380 | spelling-store-unknown-words=no 381 | 382 | 383 | [MISCELLANEOUS] 384 | 385 | # List of note tags to take in consideration, separated by a comma. 386 | notes=FIXME, 387 | XXX, 388 | TODO 389 | 390 | # Regular expression of note tags to take in consideration. 391 | #notes-rgx= 392 | 393 | 394 | [BASIC] 395 | 396 | # Naming style matching correct argument names. 397 | argument-naming-style=snake_case 398 | 399 | # Regular expression matching correct argument names. Overrides argument- 400 | # naming-style. 401 | #argument-rgx= 402 | 403 | # Naming style matching correct attribute names. 404 | attr-naming-style=snake_case 405 | 406 | # Regular expression matching correct attribute names. Overrides attr-naming- 407 | # style. 408 | #attr-rgx= 409 | 410 | # Bad variable names which should always be refused, separated by a comma. 411 | bad-names=foo, 412 | bar, 413 | baz, 414 | toto, 415 | tutu, 416 | tata 417 | 418 | # Bad variable names regexes, separated by a comma. If names match any regex, 419 | # they will always be refused 420 | bad-names-rgxs= 421 | 422 | # Naming style matching correct class attribute names. 423 | class-attribute-naming-style=any 424 | 425 | # Regular expression matching correct class attribute names. Overrides class- 426 | # attribute-naming-style. 427 | #class-attribute-rgx= 428 | 429 | # Naming style matching correct class constant names. 430 | class-const-naming-style=UPPER_CASE 431 | 432 | # Regular expression matching correct class constant names. Overrides class- 433 | # const-naming-style. 434 | #class-const-rgx= 435 | 436 | # Naming style matching correct class names. 437 | class-naming-style=PascalCase 438 | 439 | # Regular expression matching correct class names. Overrides class-naming- 440 | # style. 441 | #class-rgx= 442 | 443 | # Naming style matching correct constant names. 444 | const-naming-style=UPPER_CASE 445 | 446 | # Regular expression matching correct constant names. Overrides const-naming- 447 | # style. 448 | #const-rgx= 449 | 450 | # Minimum line length for functions/classes that require docstrings, shorter 451 | # ones are exempt. 452 | docstring-min-length=-1 453 | 454 | # Naming style matching correct function names. 455 | function-naming-style=snake_case 456 | 457 | # Regular expression matching correct function names. Overrides function- 458 | # naming-style. 459 | #function-rgx= 460 | 461 | # Good variable names which should always be accepted, separated by a comma. 462 | good-names=i, 463 | j, 464 | k, 465 | ex, 466 | Run, 467 | _ 468 | 469 | # Good variable names regexes, separated by a comma. If names match any regex, 470 | # they will always be accepted 471 | good-names-rgxs= 472 | 473 | # Include a hint for the correct naming format with invalid-name. 474 | include-naming-hint=no 475 | 476 | # Naming style matching correct inline iteration names. 477 | inlinevar-naming-style=any 478 | 479 | # Regular expression matching correct inline iteration names. Overrides 480 | # inlinevar-naming-style. 481 | #inlinevar-rgx= 482 | 483 | # Naming style matching correct method names. 484 | method-naming-style=snake_case 485 | 486 | # Regular expression matching correct method names. Overrides method-naming- 487 | # style. 488 | #method-rgx= 489 | 490 | # Naming style matching correct module names. 491 | module-naming-style=snake_case 492 | 493 | # Regular expression matching correct module names. Overrides module-naming- 494 | # style. 495 | #module-rgx= 496 | 497 | # Colon-delimited sets of names that determine each other's naming style when 498 | # the name regexes allow several styles. 499 | name-group= 500 | 501 | # Regular expression which should only match function or class names that do 502 | # not require a docstring. 503 | no-docstring-rgx=^_ 504 | 505 | # List of decorators that produce properties, such as abc.abstractproperty. Add 506 | # to this list to register other decorators that produce valid properties. 507 | # These decorators are taken in consideration only for invalid-name. 508 | property-classes=abc.abstractproperty 509 | 510 | # Naming style matching correct variable names. 511 | variable-naming-style=snake_case 512 | 513 | # Regular expression matching correct variable names. Overrides variable- 514 | # naming-style. 515 | #variable-rgx= 516 | 517 | 518 | [CLASSES] 519 | 520 | # Warn about protected attribute access inside special methods 521 | check-protected-access-in-special-methods=no 522 | 523 | # List of method names used to declare (i.e. assign) instance attributes. 524 | defining-attr-methods=__init__, 525 | __new__, 526 | setUp, 527 | __post_init__ 528 | 529 | # List of member names, which should be excluded from the protected access 530 | # warning. 531 | exclude-protected=_asdict, 532 | _fields, 533 | _replace, 534 | _source, 535 | _make 536 | 537 | # List of valid names for the first argument in a class method. 538 | valid-classmethod-first-arg=cls 539 | 540 | # List of valid names for the first argument in a metaclass class method. 541 | valid-metaclass-classmethod-first-arg=cls 542 | 543 | 544 | [DESIGN] 545 | 546 | # List of qualified class names to ignore when countint class parents (see 547 | # R0901) 548 | ignored-parents= 549 | 550 | # Maximum number of arguments for function / method. 551 | max-args=5 552 | 553 | # Maximum number of attributes for a class (see R0902). 554 | max-attributes=7 555 | 556 | # Maximum number of boolean expressions in an if statement (see R0916). 557 | max-bool-expr=5 558 | 559 | # Maximum number of branch for function / method body. 560 | max-branches=12 561 | 562 | # Maximum number of locals for function / method body. 563 | max-locals=15 564 | 565 | # Maximum number of parents for a class (see R0901). 566 | max-parents=7 567 | 568 | # Maximum number of public methods for a class (see R0904). 569 | max-public-methods=20 570 | 571 | # Maximum number of return / yield for function / method body. 572 | max-returns=6 573 | 574 | # Maximum number of statements in function / method body. 575 | max-statements=50 576 | 577 | # Minimum number of public methods for a class (see R0903). 578 | min-public-methods=2 579 | 580 | 581 | [IMPORTS] 582 | 583 | # List of modules that can be imported at any level, not just the top level 584 | # one. 585 | allow-any-import-level= 586 | 587 | # Allow wildcard imports from modules that define __all__. 588 | allow-wildcard-with-all=no 589 | 590 | # Analyse import fallback blocks. This can be used to support both Python 2 and 591 | # 3 compatible code, which means that the block might have code that exists 592 | # only in one or another interpreter, leading to false positives when analysed. 593 | analyse-fallback-blocks=no 594 | 595 | # Deprecated modules which should not be used, separated by a comma. 596 | deprecated-modules= 597 | 598 | # Output a graph (.gv or any supported image format) of external dependencies 599 | # to the given file (report RP0402 must not be disabled). 600 | ext-import-graph= 601 | 602 | # Output a graph (.gv or any supported image format) of all (i.e. internal and 603 | # external) dependencies to the given file (report RP0402 must not be 604 | # disabled). 605 | import-graph= 606 | 607 | # Output a graph (.gv or any supported image format) of internal dependencies 608 | # to the given file (report RP0402 must not be disabled). 609 | int-import-graph= 610 | 611 | # Force import order to recognize a module as part of the standard 612 | # compatibility libraries. 613 | known-standard-library= 614 | 615 | # Force import order to recognize a module as part of a third party library. 616 | known-third-party=enchant 617 | 618 | # Couples of modules and preferred modules, separated by a comma. 619 | preferred-modules= 620 | 621 | 622 | [EXCEPTIONS] 623 | 624 | # Exceptions that will emit a warning when being caught. Defaults to 625 | # "BaseException, Exception". 626 | overgeneral-exceptions=BaseException, 627 | Exception 628 | --------------------------------------------------------------------------------