├── 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 | [](https://github.com/eriwen/lcov-to-cobertura-xml/actions/workflows/ci.yml)
4 | [](https://github.com/eriwen/lcov-to-cobertura-xml/actions/workflows/sphinx.yml)
5 | [](https://github.com/eriwen/lcov-to-cobertura-xml/actions/workflows/bandit.yml)
6 | [](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 |
--------------------------------------------------------------------------------