├── .coveragerc ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE.txt ├── MANIFEST.in ├── README.rst ├── RELEASING.md ├── coverage_badge ├── __main__.py └── templates │ └── flat.svg ├── example.svg ├── media ├── 15.svg ├── 45.svg ├── 65.svg ├── 80.svg ├── 93.svg ├── 97.svg └── na.svg ├── pytest.ini ├── requirements-dev.txt ├── setup.cfg ├── setup.py └── tests ├── conftest.py └── test_output.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [report] 2 | include = coverage_badge/** 3 | omit = venv/*,VENV/* 4 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | pull_request: 6 | 7 | name: CI 8 | 9 | jobs: 10 | 11 | test: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | python: 16 | - '3.9' 17 | - '3.10' 18 | - '3.11' 19 | - '3.12' 20 | - 'pypy3.10' 21 | coverage: 22 | - '6.0' 23 | - '7.0' 24 | - '7.5' 25 | name: Python ${{ matrix.python }} on coverage ${{ matrix.coverage }} 26 | steps: 27 | - uses: actions/checkout@v4 28 | - name: Setup python ${{ matrix.python }} 29 | uses: actions/setup-python@v5 30 | with: 31 | python-version: ${{ matrix.python }} 32 | - name: Install dependencies 33 | run: pip install setuptools coverage==${{ matrix.coverage}} && pip install . && pip install -r requirements-dev.txt 34 | - name: Run tests 35 | run: python -m pytest 36 | - name: Run coverage-badge on own test coverage 37 | run: python -m coverage_badge 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.pyc 3 | __pycache__ 4 | build/ 5 | dist/ 6 | *.egg-info/ 7 | .cache/ 8 | .coverage 9 | venv/ 10 | VENV/ 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | This project follows semantic versioning. 4 | 5 | Possible log types: 6 | 7 | - `[added]` for new features. 8 | - `[changed]` for changes in existing functionality. 9 | - `[deprecated]` for once-stable features removed in upcoming releases. 10 | - `[removed]` for features removed in this release. 11 | - `[fixed]` for any bug fixes. 12 | - `[security]` to invite users to upgrade in case of vulnerabilities. 13 | - `[chore]` for maintenance changes 14 | 15 | 16 | ### v1.1.2 (2024-08-03) 17 | 18 | - [changed] Include setuptools as dependency (#31) 19 | 20 | Contributors to this version (thanks!): 21 | 22 | - [@AdrienPensart](https://github.com/AdrienPensart) 23 | 24 | Note: This project is now in maintenance mode. See `README.md` for more details. 25 | 26 | 27 | ### v1.1.1 (2024-04-24) 28 | 29 | - [fixed] Fix compatibility with coverage 7.5 (#28, #29) 30 | - [changed] Drop Python <=3.8 support 31 | 32 | Contributors to this version (thanks!): 33 | 34 | - [@danielpodrazka](https://github.com/danielpodrazka) 35 | 36 | Note: This project is now in maintenance mode. See `README.md` for more details. 37 | 38 | 39 | ### v1.1.0 (2021-11-12) 40 | 41 | - [added] Compatibility with coverage.py 6.x (#17) 42 | 43 | Contributors to this version (thanks!): 44 | 45 | - [@didorothy](https://github.com/didorothy) 46 | 47 | 48 | ### v1.0.2 (2021-10-05) 49 | 50 | - [changed] Drop Python <=3.5 support 51 | - [change] Update install requirements to include coverage (#12) 52 | - [fixed] Lock coverage.py version dependency to 5.x (#14) 53 | - [chore] Switch from TravisCI to GitHub actions 54 | - [chore] Upgrade pytest 55 | - [chore] Improve docs 56 | 57 | Contributors to this version (thanks!): 58 | 59 | - [@jackton1](https://github.com/jackton1) 60 | - [@astariul](https://github.com/astariul) 61 | 62 | 63 | ### v1.0.1 (2019-03-29) 64 | 65 | - [changed] Move Precision class into function scope 66 | 67 | 68 | ### v1.0.0 (2019-03-29) 69 | 70 | - [fixed] Use coverage package for rounding the percentage (#6) 71 | 72 | Contributors to this version (thanks!): 73 | 74 | - [@simonfagerholm](https://github.com/simonfagerholm) 75 | 76 | 77 | ### v0.2.0 (2016-11-29) 78 | 79 | - [fixed] Add XML declaration to generated SVGs (#4) 80 | - [added] Add support for multi-color badges (#3) 81 | 82 | Contributors to this version (thanks!): 83 | 84 | - [@inquire](https://github.com/inquire) 85 | - [@samael500](https://github.com/samael500) 86 | 87 | 88 | ### v0.1.2 (2015-10-13) 89 | 90 | - [fixed] Bugfix in entry point 91 | 92 | 93 | ### v0.1.1 (2015-10-13) 94 | 95 | - [chore] Set up testing 96 | 97 | 98 | ### v0.1.0 (2015-10-13) 99 | 100 | - [chore] Initial release 101 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2015-2020 Danilo Bargen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst LICENSE.txt 2 | include coverage_badge/templates/*.svg 3 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Coverage.py Badge 2 | ================== 3 | 4 | .. |buildstatus| image:: https://github.com/dbrgn/coverage-badge/workflows/CI/badge.svg 5 | :alt: Build status 6 | :target: https://github.com/dbrgn/coverage-badge/actions?query=branch%3Amain 7 | .. |downloads| image:: https://img.shields.io/pypi/dm/coverage-badge.svg 8 | :alt: PyPI Downloads 9 | :target: https://pypi.python.org/pypi/coverage-badge 10 | .. |example| image:: https://cdn.rawgit.com/dbrgn/coverage-badge/main/example.svg 11 | :alt: Example coverage badge 12 | 13 | |buildstatus| |downloads| 14 | 15 | ⚠️ coverage-badge is in maintenance mode. I might still do occasional updates 16 | and fixes from time to time, but there will be no added features. Most 17 | people using coverage-badge might want to use genbadge_ instead, which has 18 | more features (e.g. test badges, flake8 reports, etc). 19 | 20 | A small script to generate coverage badges using Coverage.py. 21 | 22 | Example of a generated badge: |example| 23 | 24 | The badge template has been taken from shields.io_, therefore it should look 25 | mostly good. (The spec is a bit stricter on the margins, but I can't easily do 26 | text width calculations in Python so the margins might not always be 4px.) 27 | 28 | .. _shields.io: http://shields.io/ 29 | .. _genbadge: https://smarie.github.io/python-genbadge/ 30 | 31 | Installation 32 | ------------ 33 | Run: 34 | 35 | .. code-block:: 36 | 37 | pip install coverage-badge 38 | 39 | 40 | Usage 41 | ----- 42 | 43 | First, run Coverage.py to generate the necessary coverage data. Then you can 44 | either return the badge SVG to stdout:: 45 | 46 | $ coverage-badge 47 | 48 | ...or write it to a file:: 49 | 50 | $ coverage-badge -o coverage.svg 51 | 52 | It's important that you run ``coverage-badge`` from the directory where the 53 | ``.coverage`` data file is located. 54 | 55 | Different colors for cover ranges: 56 | 57 | .. image:: https://cdn.rawgit.com/dbrgn/coverage-badge/main/media/15.svg 58 | :alt: 15% 59 | 60 | .. image:: https://cdn.rawgit.com/dbrgn/coverage-badge/main/media/45.svg 61 | :alt: 45% 62 | 63 | .. image:: https://cdn.rawgit.com/dbrgn/coverage-badge/main/media/65.svg 64 | :alt: 65% 65 | 66 | .. image:: https://cdn.rawgit.com/dbrgn/coverage-badge/main/media/80.svg 67 | :alt: 80% 68 | 69 | .. image:: https://cdn.rawgit.com/dbrgn/coverage-badge/main/media/93.svg 70 | :alt: 93% 71 | 72 | .. image:: https://cdn.rawgit.com/dbrgn/coverage-badge/main/media/97.svg 73 | :alt: 97% 74 | 75 | ---- 76 | 77 | The full usage text:: 78 | 79 | usage: __main__.py [-h] [-o FILEPATH] [-p] [-f] [-q] [-v] 80 | 81 | Generate coverage badges for Coverage.py. 82 | 83 | optional arguments: 84 | -h, --help show this help message and exit 85 | -o FILEPATH Save the file to the specified path. 86 | -p Plain color mode. Standard green badge. 87 | -f Force overwrite image, use with -o key. 88 | -q Don't output any non-error messages. 89 | -v Show version. 90 | 91 | License 92 | ------- 93 | 94 | MIT License, see `LICENSE.txt` file.. 95 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # Release process 2 | 3 | Signing key: https://bargen.dev/B993FF98A90C9AB1.txt 4 | 5 | Used variables: 6 | 7 | export VERSION={VERSION} 8 | export GPG=20EE002D778AE197EF7D0D2CB993FF98A90C9AB1 9 | 10 | Update version number: 11 | 12 | vim -p setup.py coverage_badge/__main__.py CHANGELOG.md 13 | 14 | Do a signed commit and signed tag of the release: 15 | 16 | git add setup.py coverage_badge/__main__.py 17 | git commit -S${GPG} -m "Release v${VERSION}" 18 | git tag -u ${GPG} -m "Release v${VERSION}" v${VERSION} 19 | 20 | Build source and binary distributions: 21 | 22 | python3 setup.py sdist 23 | python3 setup.py bdist_wheel 24 | 25 | Upload package to PyPI: 26 | 27 | twine3 upload dist/coverage[-_]badge-${VERSION}* 28 | git push 29 | git push --tags 30 | -------------------------------------------------------------------------------- /coverage_badge/__main__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Generate coverage badges for Coverage.py. 3 | """ 4 | import os 5 | import sys 6 | import argparse 7 | import pkg_resources 8 | 9 | import coverage 10 | 11 | 12 | __version__ = '1.1.2' 13 | 14 | 15 | DEFAULT_COLOR = '#a4a61d' 16 | COLORS = { 17 | 'brightgreen': '#4c1', 18 | 'green': '#97CA00', 19 | 'yellowgreen': '#a4a61d', 20 | 'yellow': '#dfb317', 21 | 'orange': '#fe7d37', 22 | 'red': '#e05d44', 23 | 'lightgrey': '#9f9f9f', 24 | } 25 | 26 | COLOR_RANGES = [ 27 | (95, 'brightgreen'), 28 | (90, 'green'), 29 | (75, 'yellowgreen'), 30 | (60, 'yellow'), 31 | (40, 'orange'), 32 | (0, 'red'), 33 | ] 34 | 35 | 36 | class Devnull(object): 37 | """ 38 | A file like object that does nothing. 39 | """ 40 | def write(self, *args, **kwargs): 41 | pass 42 | 43 | 44 | def get_total(): 45 | """ 46 | Return the rounded total as properly rounded string. 47 | """ 48 | cov = coverage.Coverage() 49 | cov.load() 50 | total = cov.report(file=Devnull()) 51 | 52 | if hasattr(coverage.results.Numbers, 'set_precision'): # Coverage <= 5 53 | class Precision(coverage.results.Numbers): 54 | """ 55 | A class for using the percentage rounding of the main coverage package, 56 | with any percentage. 57 | 58 | To get the string format of the percentage, use the ``pc_covered_str`` 59 | property. 60 | 61 | """ 62 | def __init__(self, percent): 63 | self.percent = percent 64 | 65 | @property 66 | def pc_covered(self): 67 | return self.percent 68 | 69 | return Precision(total).pc_covered_str 70 | elif hasattr(coverage.results.Numbers, 'display_covered'): # Coverage 6.x < 7.5 71 | # NOTE: Precision is no longer set globally in the 72 | # `coverage.results.Numbers` class. Instead the precision must be 73 | # passed in as the first argument. We pull the precision from the 74 | # `coverage.Coverage` object because it should pull the correct 75 | # precision from the local .coveragerc file. 76 | return coverage.results.Numbers(precision=cov.config.precision).display_covered(total) 77 | else: # Coverage >= 7.5 78 | return coverage.results.display_covered(total, cov.config.precision) 79 | 80 | 81 | 82 | def get_color(total): 83 | """ 84 | Return color for current coverage precent 85 | """ 86 | try: 87 | xtotal = int(total) 88 | except ValueError: 89 | return COLORS['lightgrey'] 90 | for range_, color in COLOR_RANGES: 91 | if xtotal >= range_: 92 | return COLORS[color] 93 | 94 | 95 | def get_badge(total, color=DEFAULT_COLOR): 96 | """ 97 | Read the SVG template from the package, update total, return SVG as a 98 | string. 99 | """ 100 | template_path = os.path.join('templates', 'flat.svg') 101 | template = pkg_resources.resource_string(__name__, template_path).decode('utf8') 102 | return template.replace('{{ total }}', total).replace('{{ color }}', color) 103 | 104 | 105 | def parse_args(argv=None): 106 | """ 107 | Parse the command line arguments. 108 | """ 109 | parser = argparse.ArgumentParser(description=__doc__) 110 | parser.add_argument('-o', dest='filepath', 111 | help='Save the file to the specified path.') 112 | parser.add_argument('-p', dest='plain_color', action='store_true', 113 | help='Plain color mode. Standard green badge.') 114 | parser.add_argument('-f', dest='force', action='store_true', 115 | help='Force overwrite image, use with -o key.') 116 | parser.add_argument('-q', dest='quiet', action='store_true', 117 | help='Don\'t output any non-error messages.') 118 | parser.add_argument('-v', dest='print_version', action='store_true', 119 | help='Show version.') 120 | 121 | # If arguments have been passed in, use them. 122 | if argv: 123 | return parser.parse_args(argv) 124 | 125 | # Otherwise, just use sys.argv directly. 126 | else: 127 | return parser.parse_args() 128 | 129 | 130 | def save_badge(badge, filepath, force=False): 131 | """ 132 | Save badge to the specified path. 133 | """ 134 | # Validate path (part 1) 135 | if filepath.endswith('/'): 136 | print('Error: Filepath may not be a directory.') 137 | sys.exit(1) 138 | 139 | # Get absolute filepath 140 | path = os.path.abspath(filepath) 141 | if not path.lower().endswith('.svg'): 142 | path += '.svg' 143 | 144 | # Validate path (part 2) 145 | if not force and os.path.exists(path): 146 | print('Error: "{}" already exists.'.format(path)) 147 | sys.exit(1) 148 | 149 | # Write file 150 | with open(path, 'w') as f: 151 | f.write(badge) 152 | 153 | return path 154 | 155 | 156 | def main(argv=None): 157 | """ 158 | Console scripts entry point. 159 | """ 160 | args = parse_args(argv) 161 | 162 | # Print version 163 | if args.print_version: 164 | print('coverage-badge v{}'.format(__version__)) 165 | sys.exit(0) 166 | 167 | # Check for coverage 168 | if coverage is None: 169 | print('Error: Python coverage module not installed.') 170 | sys.exit(1) 171 | 172 | # Generate badge 173 | try: 174 | total = get_total() 175 | except coverage.misc.CoverageException as e: 176 | print('Error: {} Did you run coverage first?'.format(e)) 177 | sys.exit(1) 178 | 179 | color = DEFAULT_COLOR if args.plain_color else get_color(total) 180 | badge = get_badge(total, color) 181 | 182 | # Show or save output 183 | if args.filepath: 184 | path = save_badge(badge, args.filepath, args.force) 185 | if not args.quiet: 186 | print('Saved badge to {}'.format(path)) 187 | else: 188 | print(badge, end='') 189 | 190 | 191 | if __name__ == '__main__': 192 | main() 193 | -------------------------------------------------------------------------------- /coverage_badge/templates/flat.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | coverage 17 | coverage 18 | {{ total }}% 19 | {{ total }}% 20 | 21 | 22 | -------------------------------------------------------------------------------- /example.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | coverage 17 | coverage 18 | 24% 19 | 24% 20 | 21 | 22 | -------------------------------------------------------------------------------- /media/15.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | coverage 17 | coverage 18 | 15% 19 | 15% 20 | 21 | 22 | -------------------------------------------------------------------------------- /media/45.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | coverage 17 | coverage 18 | 45% 19 | 45% 20 | 21 | 22 | -------------------------------------------------------------------------------- /media/65.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | coverage 17 | coverage 18 | 65% 19 | 65% 20 | 21 | 22 | -------------------------------------------------------------------------------- /media/80.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | coverage 17 | coverage 18 | 80% 19 | 80% 20 | 21 | 22 | -------------------------------------------------------------------------------- /media/93.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | coverage 17 | coverage 18 | 93% 19 | 93% 20 | 21 | 22 | -------------------------------------------------------------------------------- /media/97.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | coverage 17 | coverage 18 | 97% 19 | 97% 20 | 21 | 22 | -------------------------------------------------------------------------------- /media/na.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | coverage 17 | coverage 18 | na% 19 | na% 20 | 21 | 22 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = --tb=short --cov 3 | python_files = test_*.py 4 | norecursedirs = *.egg tmp* build .tox venv VENV 5 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | pytest==6.2.5 2 | pytest-cache==1.0 3 | pytest-cov==3.0.0 4 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal=1 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | readme = open('README.rst').read() 4 | 5 | setup(name='coverage-badge', 6 | version='1.1.2', 7 | description='Generate coverage badges for Coverage.py.', 8 | author='Danilo Bargen', 9 | author_email='mail@dbrgn.ch', 10 | url='https://github.com/dbrgn/coverage-badge', 11 | install_requires=['coverage', 'setuptools'], 12 | packages=['coverage_badge'], 13 | zip_safe=True, 14 | include_package_data=True, 15 | license='MIT', 16 | keywords='coverage badge shield', 17 | long_description=readme, 18 | entry_points={ 19 | 'console_scripts': [ 20 | 'coverage-badge = coverage_badge.__main__:main', 21 | ] 22 | }, 23 | classifiers=[ 24 | 'Development Status :: 5 - Production/Stable', 25 | 'Environment :: Console', 26 | 'License :: OSI Approved :: MIT License', 27 | 'Operating System :: OS Independent', 28 | 'Programming Language :: Python :: 3', 29 | 'Topic :: Software Development :: Testing', 30 | ], 31 | ) 32 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import print_function, division, absolute_import, unicode_literals 3 | 4 | import sys 5 | import os 6 | 7 | current_dir = os.path.dirname(os.path.abspath(__file__)) 8 | parent_dir = os.path.normpath(os.path.join(current_dir, '..')) 9 | sys.path.insert(0, parent_dir) 10 | -------------------------------------------------------------------------------- /tests/test_output.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import print_function, division, absolute_import, unicode_literals 3 | 4 | import sys 5 | from textwrap import dedent 6 | 7 | import pytest 8 | 9 | from coverage_badge import __main__ 10 | 11 | 12 | @pytest.fixture 13 | def cb(monkeypatch): 14 | """ 15 | Return a monkey patched coverage_badge module that always returns a percentage of 79. 16 | """ 17 | def get_fake_total(): 18 | return '79' 19 | monkeypatch.setattr(__main__, 'get_total', get_fake_total) 20 | return __main__ 21 | 22 | 23 | def test_version(cb, capsys): 24 | """ 25 | Test the version output. 26 | """ 27 | with pytest.raises(SystemExit) as se: 28 | cb.main(['-v']) 29 | out, _ = capsys.readouterr() 30 | assert out == 'coverage-badge v%s\n' % __main__.__version__ 31 | 32 | 33 | def test_svg_output(cb, capsys): 34 | """ 35 | Test the SVG output. 36 | """ 37 | cb.main([]) 38 | out, _ = capsys.readouterr() 39 | assert out.startswith('') 40 | assert '' in out 41 | assert '79%' in out 42 | assert out.endswith('\n') 43 | 44 | 45 | def test_color_ranges(cb, capsys): 46 | """ 47 | Test color total value 48 | """ 49 | for total, color in (('97', '#4c1'), ('93', '#97CA00'), ('80', '#a4a61d'), ('65', '#dfb317'), 50 | ('45', '#fe7d37'), ('15', '#e05d44'), ('n/a', '#9f9f9f')): 51 | __main__.get_total = lambda: total 52 | cb.main([]) 53 | out, _ = capsys.readouterr() 54 | row = '' % color 55 | assert out.startswith(dedent('''\ 56 | 57 | ''')) 58 | assert row in out 59 | assert out.endswith('\n') 60 | 61 | 62 | def test_plain_color_mode(cb, capsys): 63 | """ 64 | Should get always one color in badge 65 | """ 66 | assert __main__.DEFAULT_COLOR == '#a4a61d' 67 | for total in ('97', '93', '80', '65', '45', '15', 'n/a'): 68 | __main__.get_total = lambda: total 69 | cb.main(['-p']) 70 | out, _ = capsys.readouterr() 71 | row = '' 72 | assert out.startswith(dedent('''\ 73 | 74 | ''')) 75 | assert row in out 76 | assert out.endswith('\n') 77 | --------------------------------------------------------------------------------