././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1706812216.833058 pytest-sugar-1.0.0/0000755000175100001770000000000014556761471013604 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1706812216.0 pytest-sugar-1.0.0/CHANGES.rst0000644000175100001770000000750514556761470015414 0ustar00runnerdockerChangelog --------- 1.0.0 - 2024-02-01 ^^^^^^^^^^^^^^^^^^ * Add support for pytest 8.x * Drop support for Python 3.7 Contributed by [Justin Mayer](https://github.com/justinmayer) via [PR #281](https://github.com/Teemu/pytest-sugar/pull/281/) 0.9.7 - 2023-04-10 ^^^^^^^^^^^^^^^^^^ - For long-running tests, display minutes and not only seconds (thanks @last-partizan) - Add support for Pytest’s ``--header`` option (thanks @wiresv) 0.9.6 (2022-11-5) ^^^^^^^^^^^^^^^^^^^ - Remove py.std calls (thanks @alexcjohnson) 0.9.5 (2022-07-10) ^^^^^^^^^^^^^^^^^^^ - Fix distutils deprecation warning (thanks @tgagor) - Fix incompatibility with pytest-timeout (thanks @graingert) - Update pytest naming convention in documentation (thanks @avallbona) 0.9.4 (2020-07-06) ^^^^^^^^^^^^^^^^^^^ - Fix pytest-sugar 0.9.3 incompatible with pytest 5.4 (thanks @nicoddemus) - Fix Tests fail with pytest 3.5.0 DOCTESTS (^) - Fix Tests fail with pytest 5.x (^) 0.9.3 (2020-04-26) ^^^^^^^^^^^^^^^^^^^ - Fix incompatibility with pytest 5.4.0 (thanks @GuillaumeFavelier) 0.9.2 (2018-11-8) ^^^^^^^^^^^^^^^^^^^ - Fix incompatibility with pytest 3.10 (thanks @Natim) - Double colons for verbose output (thanks @albertodonato) - Fix "Wrong count with items modified in pytest_collection_modifyitems" (thanks @blueyed) - Defer registration of xdist hook (thanks @blueyed) 0.9.1 (2017-8-4) ^^^^^^^^^^^^^^^^^^^ - Fix incompatibility with pytest 3.4 (thanks @nicoddemus) 0.9.0 (2017-8-4) ^^^^^^^^^^^^^^^^^^^ - Print correct location for doctest failures - Write xdist output on correct lines 0.8.0 (2016-12-28) ^^^^^^^^^^^^^^^^^^^ - Release as an universal wheel - Pytest3 compatibility - Treat setup/teardown failures as errors - Fix path issue in --new-summary - Disable sugar output when not in terminal, should help with testing other pytest plugins - Add double colons when in verbose mode - Make --new-summary default, replaced flag with --old-summary 0.7.1 (2016-4-1) ^^^^^^^^^^^^^^^^^^^ - Fix issue with deselected tests 0.7.0 (2016-3-29) ^^^^^^^^^^^^^^^^^^^ - Show skipped tests - Changed failed test summary (try `--new-summary` option to test it out) - Show teardown errors - Add support for pytest-rerunfailedtests - Make test symbols customizable - Remove deprecated `--nosugar`. 0.6.0 (2016-3-18) ^^^^^^^^^^^^^^^^^^^ - pytest-xdist support - Turn off progress meter when progressbar_length=0 0.5.1 (2015-10-12) ^^^^^^^^^^^^^^^^^^^ - Fix Python 3 support 0.5.0 (2015-10-12) ^^^^^^^^^^^^^^^^^^^ - Colour progressbar correctly for low number of tests - Fix error case when deactivating pytest-sugar using --lf together with --nosugar - --nosugar deprecated, use -p no:sugar 0.4.0 (2015-03-25) ^^^^^^^^^^^^^^^^^^^ Thanks to or: - Configurable colors - Handling of long file paths - Red progressbar in case of failures - Using termcolor for much easier coloration and configuration - Simplify the progressbar maths code - Change the 's' for skipped tests to a circle - Simplify the space filling logic of full_line - Reduce the right margin to 0, so the blinking cursor is hidden 0.3.6 (2014-12-12) ^^^^^^^^^^^^^^^^^^^ - Crashline with non-ASCII, #42 - Restore Python 2.6 / 3.3 support - Fix unit tests - Fix UnicodeDecodeError during install, #43 0.3.5 (2014-11-26) ^^^^^^^^^^^^^^^^^^^ - Fix codec error during pip install 0.3.4 (2014-04-02) ^^^^^^^^^^^^^^^^^^^ - Using pytest.mark.xfails throws an error #34 0.3.3 (2014-02-14) ^^^^^^^^^^^^^^^^^^^ - Fix problem with PyPi package. 0.3.2 (2014-02-06) ^^^^^^^^^^^^^^^^^^^ - Fix issue with PyPI package. - Code refactoring 0.3.1 (2014-02-06) ^^^^^^^^^^^^^^^^^^^ - Fix incorrect wrapping that fine-grained progress introduced 0.3.0 (2014-6-05) ^^^^^^^^^^^^^^^^^^^ - Fine-grained progressbar using more Unicode block chars - Display version of pytest and pytest-sugar - Python 3 support - Fix GH-3: Wrap tests when they extend past line ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1706812202.0 pytest-sugar-1.0.0/CONTRIBUTORS.rst0000644000175100001770000000035314556761452016273 0ustar00runnerdockerThe following people have contributed to pytest-sugar: * Janne Vanhala * Teemu * Marc Abramowitz * `Justin Mayer `_ * Yizhe Tang * Mahdi Yusuf * dscerri * Mounier Florian * Balthazar Rouberol * Michael Howitz ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1706812202.0 pytest-sugar-1.0.0/LICENSE0000644000175100001770000000272214556761452014613 0ustar00runnerdockerCopyright (c) 2013, Teemu Copyright (c) 2013, Janne Vanhala All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The names of the contributors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1706812202.0 pytest-sugar-1.0.0/MANIFEST.in0000644000175100001770000000013514556761452015340 0ustar00runnerdockerinclude README.md CONTRIBUTORS.rst CHANGES.rst LICENSE include test_sugar.py include tox.ini ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1706812216.833058 pytest-sugar-1.0.0/PKG-INFO0000644000175100001770000001014114556761471014676 0ustar00runnerdockerMetadata-Version: 2.1 Name: pytest-sugar Version: 1.0.0 Summary: pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly). Home-page: https://github.com/Teemu/pytest-sugar Author: Teemu, Janne Vanhala and others Author-email: orkkiolento@gmail.com, janne.vanhala@gmail.com License: BSD Project-URL: Issue Tracker, https://github.com/Teemu/pytest-sugar/issues Platform: any Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: POSIX Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: MacOS :: MacOS X Classifier: Topic :: Software Development :: Testing Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Utilities Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: Implementation :: PyPy Description-Content-Type: text/markdown Provides-Extra: dev License-File: LICENSE # pytest-sugar ✨ [![Build Status](https://img.shields.io/github/actions/workflow/status/Teemu/pytest-sugar/build-and-test.yaml?branch=main)](https://github.com/Teemu/pytest-sugar/actions) [![PyPI Version](https://img.shields.io/pypi/v/pytest-sugar.svg)](https://pypi.org/project/pytest-sugar/) [![Downloads](https://img.shields.io/pypi/dm/pytest-sugar)](https://pypi.org/project/pytest-sugar/) ![License](https://img.shields.io/pypi/l/pytest-sugar?color=blue) This plugin extends [pytest](http://pytest.org) by showing failures and errors instantly, adding a progress bar, improving the test results, and making the output look better. ![render1667890332624-min](https://user-images.githubusercontent.com/53298/200600769-7b871b26-a36a-4ae6-ae24-945ee83fb74a.gif) ## Installation To install pytest-sugar: python -m pip install pytest-sugar Once installed, the plugin is activated automatically. Run your tests normally: pytest If you would like more detailed output (one test per line), then you may use the verbose option: pytest --verbose If you would like to run tests without pytest-sugar, use: pytest -p no:sugar ## How to contribute πŸ‘·β€β™‚οΈ [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&ref=master&repo=10950375) Make sure to read our [Code of Conduct](https://github.com/Teemu/pytest-sugar/blob/master/.github/CODE_OF_CONDUCT.md). You can get started modifying the codebase with the following commands. Alternatively, you can try Github Codespaces (click the badge above). Push the changes to your repository & create a pull request. ```` git clone git@github.com:Teemu/pytest-sugar.git cd pytest-sugar python -m venv .venv source .venv/bin/activate echo ".venv" >> .git/info/exclude pip install -e ".[dev]" pre-commit install ```` There are two ways of running tests. We have our proper tests: ```` pytest . ```` There are also fake tests that can be used to visualise the output: ```` pytest faketests ```` When submitting a pull request, please add a `RELEASE.md` file in the root of the project that contains the release type (major, minor, patch) and a summary of the changes that will be used as the release changelog entry. For example: ```markdown Release type: patch For long-running tests, display minutes and not only seconds. ``` ## Requirements You will need the following prerequisites in order to use pytest-sugar: - Python 3.8 or newer - pytest 6.2 or newer ## Running on Windows If you are seeing gibberish, you might want to try changing charset and fonts. See [this comment]( https://github.com/Teemu/pytest-sugar/pull/49#issuecomment-146567670) for more details. ## Similar projects - [pytest-rich](https://github.com/nicoddemus/pytest-rich) - [pytest-pretty](https://github.com/samuelcolvin/pytest-pretty) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1706812202.0 pytest-sugar-1.0.0/README.md0000644000175100001770000000546514556761452015074 0ustar00runnerdocker# pytest-sugar ✨ [![Build Status](https://img.shields.io/github/actions/workflow/status/Teemu/pytest-sugar/build-and-test.yaml?branch=main)](https://github.com/Teemu/pytest-sugar/actions) [![PyPI Version](https://img.shields.io/pypi/v/pytest-sugar.svg)](https://pypi.org/project/pytest-sugar/) [![Downloads](https://img.shields.io/pypi/dm/pytest-sugar)](https://pypi.org/project/pytest-sugar/) ![License](https://img.shields.io/pypi/l/pytest-sugar?color=blue) This plugin extends [pytest](http://pytest.org) by showing failures and errors instantly, adding a progress bar, improving the test results, and making the output look better. ![render1667890332624-min](https://user-images.githubusercontent.com/53298/200600769-7b871b26-a36a-4ae6-ae24-945ee83fb74a.gif) ## Installation To install pytest-sugar: python -m pip install pytest-sugar Once installed, the plugin is activated automatically. Run your tests normally: pytest If you would like more detailed output (one test per line), then you may use the verbose option: pytest --verbose If you would like to run tests without pytest-sugar, use: pytest -p no:sugar ## How to contribute πŸ‘·β€β™‚οΈ [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&ref=master&repo=10950375) Make sure to read our [Code of Conduct](https://github.com/Teemu/pytest-sugar/blob/master/.github/CODE_OF_CONDUCT.md). You can get started modifying the codebase with the following commands. Alternatively, you can try Github Codespaces (click the badge above). Push the changes to your repository & create a pull request. ```` git clone git@github.com:Teemu/pytest-sugar.git cd pytest-sugar python -m venv .venv source .venv/bin/activate echo ".venv" >> .git/info/exclude pip install -e ".[dev]" pre-commit install ```` There are two ways of running tests. We have our proper tests: ```` pytest . ```` There are also fake tests that can be used to visualise the output: ```` pytest faketests ```` When submitting a pull request, please add a `RELEASE.md` file in the root of the project that contains the release type (major, minor, patch) and a summary of the changes that will be used as the release changelog entry. For example: ```markdown Release type: patch For long-running tests, display minutes and not only seconds. ``` ## Requirements You will need the following prerequisites in order to use pytest-sugar: - Python 3.8 or newer - pytest 6.2 or newer ## Running on Windows If you are seeing gibberish, you might want to try changing charset and fonts. See [this comment]( https://github.com/Teemu/pytest-sugar/pull/49#issuecomment-146567670) for more details. ## Similar projects - [pytest-rich](https://github.com/nicoddemus/pytest-rich) - [pytest-pretty](https://github.com/samuelcolvin/pytest-pretty) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1706812215.0 pytest-sugar-1.0.0/pyproject.toml0000644000175100001770000000260414556761467016527 0ustar00runnerdocker[tool.poetry] name = "pytest-sugar" version = "1.0.0" description = "Pytest plugin that adds a progress bar and other visual enhancements" authors = ["Teemu ", "Janne Vanhala "] license = "BSD 3-Clause" readme = "README.md" keywords = ["testing", "pytest", "plugin"] classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Topic :: Software Development :: Testing", "Topic :: Software Development :: Libraries", "Topic :: Utilities", ] [tool.poetry.urls] Homepage = "https://github.com/Teemu/pytest-sugar" "Issue Tracker" = "https://github.com/Teemu/pytest-sugar/issues" [tool.poetry.dependencies] python = ">=3.8.1,<4.0" pytest = ">=6.2.0" termcolor = ">=2.1.0" packaging = ">=21.3" [tool.poetry.dev-dependencies] black = "^24.0" flake8 = "^7.0" isort = "^5.13" [tool.autopub] project-name = "pytest-sugar" git-username = "botpub" git-email = "52496925+botpub@users.noreply.github.com" append-github-contributor = true changelog-file = "CHANGES.rst" changelog-header = "---------" version-header = "^" version-strings = ["pytest_sugar.py"] tag-prefix = "v" build-system = "setuptools" [tool.isort] profile = "black" [build-system] requires = ["setuptools >= 40.6.0", "wheel"] ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1706812216.833058 pytest-sugar-1.0.0/pytest_sugar.egg-info/0000755000175100001770000000000014556761471020027 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1706812216.0 pytest-sugar-1.0.0/pytest_sugar.egg-info/PKG-INFO0000644000175100001770000001014114556761470021120 0ustar00runnerdockerMetadata-Version: 2.1 Name: pytest-sugar Version: 1.0.0 Summary: pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly). Home-page: https://github.com/Teemu/pytest-sugar Author: Teemu, Janne Vanhala and others Author-email: orkkiolento@gmail.com, janne.vanhala@gmail.com License: BSD Project-URL: Issue Tracker, https://github.com/Teemu/pytest-sugar/issues Platform: any Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: POSIX Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: MacOS :: MacOS X Classifier: Topic :: Software Development :: Testing Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Utilities Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: Implementation :: PyPy Description-Content-Type: text/markdown Provides-Extra: dev License-File: LICENSE # pytest-sugar ✨ [![Build Status](https://img.shields.io/github/actions/workflow/status/Teemu/pytest-sugar/build-and-test.yaml?branch=main)](https://github.com/Teemu/pytest-sugar/actions) [![PyPI Version](https://img.shields.io/pypi/v/pytest-sugar.svg)](https://pypi.org/project/pytest-sugar/) [![Downloads](https://img.shields.io/pypi/dm/pytest-sugar)](https://pypi.org/project/pytest-sugar/) ![License](https://img.shields.io/pypi/l/pytest-sugar?color=blue) This plugin extends [pytest](http://pytest.org) by showing failures and errors instantly, adding a progress bar, improving the test results, and making the output look better. ![render1667890332624-min](https://user-images.githubusercontent.com/53298/200600769-7b871b26-a36a-4ae6-ae24-945ee83fb74a.gif) ## Installation To install pytest-sugar: python -m pip install pytest-sugar Once installed, the plugin is activated automatically. Run your tests normally: pytest If you would like more detailed output (one test per line), then you may use the verbose option: pytest --verbose If you would like to run tests without pytest-sugar, use: pytest -p no:sugar ## How to contribute πŸ‘·β€β™‚οΈ [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&ref=master&repo=10950375) Make sure to read our [Code of Conduct](https://github.com/Teemu/pytest-sugar/blob/master/.github/CODE_OF_CONDUCT.md). You can get started modifying the codebase with the following commands. Alternatively, you can try Github Codespaces (click the badge above). Push the changes to your repository & create a pull request. ```` git clone git@github.com:Teemu/pytest-sugar.git cd pytest-sugar python -m venv .venv source .venv/bin/activate echo ".venv" >> .git/info/exclude pip install -e ".[dev]" pre-commit install ```` There are two ways of running tests. We have our proper tests: ```` pytest . ```` There are also fake tests that can be used to visualise the output: ```` pytest faketests ```` When submitting a pull request, please add a `RELEASE.md` file in the root of the project that contains the release type (major, minor, patch) and a summary of the changes that will be used as the release changelog entry. For example: ```markdown Release type: patch For long-running tests, display minutes and not only seconds. ``` ## Requirements You will need the following prerequisites in order to use pytest-sugar: - Python 3.8 or newer - pytest 6.2 or newer ## Running on Windows If you are seeing gibberish, you might want to try changing charset and fonts. See [this comment]( https://github.com/Teemu/pytest-sugar/pull/49#issuecomment-146567670) for more details. ## Similar projects - [pytest-rich](https://github.com/nicoddemus/pytest-rich) - [pytest-pretty](https://github.com/samuelcolvin/pytest-pretty) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1706812216.0 pytest-sugar-1.0.0/pytest_sugar.egg-info/SOURCES.txt0000644000175100001770000000056514556761470021720 0ustar00runnerdockerCHANGES.rst CONTRIBUTORS.rst LICENSE MANIFEST.in README.md pyproject.toml pytest_sugar.py setup.py test_sugar.py tox.ini pytest_sugar.egg-info/PKG-INFO pytest_sugar.egg-info/SOURCES.txt pytest_sugar.egg-info/dependency_links.txt pytest_sugar.egg-info/entry_points.txt pytest_sugar.egg-info/not-zip-safe pytest_sugar.egg-info/requires.txt pytest_sugar.egg-info/top_level.txt././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1706812216.0 pytest-sugar-1.0.0/pytest_sugar.egg-info/dependency_links.txt0000644000175100001770000000000114556761470024074 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1706812216.0 pytest-sugar-1.0.0/pytest_sugar.egg-info/entry_points.txt0000644000175100001770000000004114556761470023317 0ustar00runnerdocker[pytest11] sugar = pytest_sugar ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1706812216.0 pytest-sugar-1.0.0/pytest_sugar.egg-info/not-zip-safe0000644000175100001770000000000114556761470022254 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1706812216.0 pytest-sugar-1.0.0/pytest_sugar.egg-info/requires.txt0000644000175100001770000000011614556761470022424 0ustar00runnerdockerpytest>=6.2.0 termcolor>=2.1.0 packaging>=21.3 [dev] black flake8 pre-commit ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1706812216.0 pytest-sugar-1.0.0/pytest_sugar.egg-info/top_level.txt0000644000175100001770000000001514556761470022554 0ustar00runnerdockerpytest_sugar ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1706812215.0 pytest-sugar-1.0.0/pytest_sugar.py0000644000175100001770000005541514556761467016726 0ustar00runnerdocker""" pytest_sugar ~~~~~~~~~~~~ pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly). :copyright: see LICENSE for details :license: BSD, see LICENSE for more details. """ import dataclasses import locale import os import re import sys import time from configparser import ConfigParser # type: ignore from typing import Any, Dict, Generator, List, Optional, Sequence, Tuple, Union import pytest from _pytest.config.argparsing import Parser from _pytest.main import Session from _pytest.nodes import Item from _pytest.reports import BaseReport, CollectReport, TestReport from _pytest.terminal import TerminalReporter, format_session_duration from termcolor import colored __version__ = "1.0.0" LEN_RIGHT_MARGIN = 0 LEN_PROGRESS_PERCENTAGE = 5 LEN_PROGRESS_BAR_SETTING = "10" LEN_PROGRESS_BAR: Optional[int] = None @dataclasses.dataclass class Theme: header: Optional[str] = "magenta" skipped: Optional[str] = "blue" success: Optional[str] = "green" warning: Optional[str] = "yellow" fail: Optional[str] = "red" error: Optional[str] = "red" xfailed: Optional[str] = "green" xpassed: Optional[str] = "red" progressbar: Optional[str] = "green" progressbar_fail: Optional[str] = "red" progressbar_background: Optional[str] = "grey" path: Optional[str] = "cyan" name = None symbol_passed: str = "βœ“" symbol_skipped: str = "s" symbol_failed: str = "β¨―" symbol_failed_not_call: str = "β‚“" symbol_xfailed_skipped: str = "x" symbol_xfailed_failed: str = "X" symbol_unknown: str = "?" unknown: Optional[str] = "blue" symbol_rerun: Optional[str] = "R" rerun: Optional[str] = "blue" def __getitem__(self, x): return getattr(self, x) THEME: Theme = Theme() PROGRESS_BAR_BLOCKS: List[str] = [ " ", "▏", "β–Ž", "β–Ž", "▍", "▍", "β–Œ", "β–Œ", "β–‹", "β–‹", "β–Š", "β–Š", "β–‰", "β–‰", "β–ˆ", ] def flatten(seq) -> Generator[Any, None, None]: for x in seq: if isinstance(x, (list, tuple)): yield from flatten(x) else: yield x def pytest_collection_finish(session: Session) -> None: reporter = session.config.pluginmanager.getplugin("terminalreporter") if reporter: reporter.tests_count = len(session.items) class DeferredXdistPlugin: def pytest_xdist_node_collection_finished(self, node, ids) -> None: terminal_reporter = node.config.pluginmanager.getplugin("terminalreporter") if terminal_reporter: terminal_reporter.tests_count = len(ids) def pytest_deselected(items: Sequence[Item]) -> None: """Update tests_count to not include deselected tests""" if len(items) > 0: pluginmanager = items[0].config.pluginmanager terminal_reporter = pluginmanager.getplugin("terminalreporter") if ( hasattr(terminal_reporter, "tests_count") and terminal_reporter.tests_count > 0 ): terminal_reporter.tests_count -= len(items) def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("terminal reporting", "reporting", after="general") group._addoption( "--old-summary", action="store_true", dest="tb_summary", default=False, help=("Show tests that failed instead of one-line tracebacks"), ) group._addoption( "--force-sugar", action="store_true", dest="force_sugar", default=False, help=("Force pytest-sugar output even when not in real terminal"), ) def pytest_sessionstart(session: Session) -> None: global THEME, LEN_PROGRESS_BAR_SETTING config = ConfigParser() config.read(["pytest-sugar.conf", os.path.expanduser("~/.pytest-sugar.conf")]) theme_attributes: Dict[str, Optional[str]] = {} fields: Tuple[dataclasses.Field, ...] = dataclasses.fields(Theme) for field in fields: key = field.name if not config.has_option("theme", key): continue value_str: str = config.get("theme", key).lower() value: Optional[str] = value_str if value in ("", "none"): value = None theme_attributes[key] = value if config.has_option("sugar", "progressbar_length"): LEN_PROGRESS_BAR_SETTING = config.get("sugar", "progressbar_length") THEME = Theme(**theme_attributes) # type: ignore def strip_colors(text: str) -> str: ansi_escape = re.compile(r"\x1b[^m]*m") stripped = ansi_escape.sub("", text) return stripped def real_string_length(string: str) -> int: return len(strip_colors(string)) IS_SUGAR_ENABLED = False @pytest.hookimpl(trylast=True) def pytest_configure(config) -> None: global IS_SUGAR_ENABLED if sys.stdout.isatty() or config.getvalue("force_sugar"): IS_SUGAR_ENABLED = True if config.pluginmanager.hasplugin("xdist"): try: import xdist # type: ignore except ImportError: pass else: from packaging import version xdist_version = version.Version(xdist.__version__) if xdist_version >= version.Version("1.14"): config.pluginmanager.register(DeferredXdistPlugin()) if IS_SUGAR_ENABLED and not getattr(config, "slaveinput", None): # Get the standard terminal reporter plugin and replace it with our standard_reporter = config.pluginmanager.getplugin("terminalreporter") sugar_reporter = SugarTerminalReporter(standard_reporter) config.pluginmanager.unregister(standard_reporter) config.pluginmanager.register(sugar_reporter, "terminalreporter") def pytest_report_teststatus(report: BaseReport) -> Optional[Tuple[str, str, str]]: if not IS_SUGAR_ENABLED: return None if report.passed: letter = colored(THEME.symbol_passed, THEME.success) elif report.skipped: letter = colored(THEME.symbol_skipped, THEME.skipped) elif report.failed: letter = colored(THEME.symbol_failed, THEME.fail) if report.when != "call": letter = colored(THEME.symbol_failed_not_call, THEME.fail) elif report.outcome == "rerun": letter = colored(THEME.symbol_rerun, THEME.rerun) else: letter = colored(THEME.symbol_unknown, THEME.unknown) if hasattr(report, "wasxfail"): if report.skipped: return ( "xfailed", colored(THEME.symbol_xfailed_skipped, THEME.xfailed), "xfail", ) if report.passed: return ( "xpassed", colored(THEME.symbol_xfailed_failed, THEME.xpassed), "XPASS", ) return report.outcome, letter, report.outcome.upper() class SugarTerminalReporter(TerminalReporter): # type: ignore def __init__(self, reporter) -> None: TerminalReporter.__init__(self, reporter.config) self.paths_left = [] self.tests_count = 0 self.tests_taken = 0 self.reports = [] self.unreported_errors = [] self.progress_blocks = [] self.reset_tracked_lines() def reset_tracked_lines(self) -> None: self.current_lines = {} self.current_line_nums = {} self.current_line_num = 0 def pytest_collectreport(self, report: CollectReport) -> None: TerminalReporter.pytest_collectreport(self, report) if report.location[0]: self.paths_left.append(os.path.join(os.getcwd(), report.location[0])) if report.failed: self.rewrite("") self.print_failure(report) def pytest_sessionstart(self, session: Session) -> None: self._session = session self._sessionstarttime = time.time() if self.no_header: return verinfo = ".".join(map(str, sys.version_info[:3])) self.write_line( "Test session starts " "(platform: %s, Python %s, pytest %s, pytest-sugar %s)" % ( sys.platform, verinfo, pytest.__version__, __version__, ), bold=True, ) if int(pytest.__version__.split(".")[0]) <= 6: hook_call_kwargs = {"startdir": self.startpath} else: hook_call_kwargs = {"start_path": self.startpath} lines = self.config.hook.pytest_report_header( config=self.config, **hook_call_kwargs ) lines.reverse() for line in flatten(lines): self.write_line(line) def write_fspath_result(self, nodeid: str, res, **markup: bool) -> None: return def insert_progress(self, report: Union[CollectReport, TestReport]) -> None: def get_progress_bar() -> str: length = LEN_PROGRESS_BAR if not length: return "" p = float(self.tests_taken) / self.tests_count if self.tests_count else 0 floored = int(p * length) rem = int(round((p * length - floored) * (len(PROGRESS_BAR_BLOCKS) - 1))) progressbar = "%i%% " % round(p * 100) # make sure we only report 100% at the last test if progressbar == "100% " and self.tests_taken < self.tests_count: progressbar = "99% " # if at least one block indicates failure, # then the percentage should reflect that if [1 for block, success in self.progress_blocks if not success]: progressbar = colored(progressbar, THEME.fail) else: progressbar = colored(progressbar, THEME.success) bar = PROGRESS_BAR_BLOCKS[-1] * floored if rem > 0: bar += PROGRESS_BAR_BLOCKS[rem] bar += " " * (LEN_PROGRESS_BAR - len(bar)) last = 0 last_theme = None progressbar_background = THEME.progressbar_background if progressbar_background is None: on_color = None else: on_color = "on_" + progressbar_background for block, success in self.progress_blocks: if success: theme = THEME.progressbar else: theme = THEME.progressbar_fail if last < block: progressbar += colored(bar[last:block], last_theme, on_color) progressbar += colored(bar[block], theme, on_color) last = block + 1 last_theme = theme if last < len(bar): progressbar += colored(bar[last : len(bar)], last_theme, on_color) return progressbar append_string = get_progress_bar() path = self.report_key(report) current_line = self.current_lines.get(path, "") line_num = self.current_line_nums.get(path, self.current_line_num) console_width = self._tw.fullwidth num_spaces = ( console_width - real_string_length(current_line) - real_string_length(append_string) - LEN_RIGHT_MARGIN ) full_line = current_line + " " * num_spaces full_line += append_string self.overwrite(full_line, self.current_line_num - line_num) def overwrite(self, line: str, rel_line_num: int) -> None: # Move cursor up rel_line_num lines if rel_line_num > 0: self.write("\033[%dA" % rel_line_num) # Overwrite the line self.write(f"\r{line}") # Return cursor to original line if rel_line_num > 0: self.write("\033[%dB" % rel_line_num) def get_max_column_for_test_status(self) -> int: assert LEN_PROGRESS_BAR return ( self._tw.fullwidth - LEN_PROGRESS_PERCENTAGE - LEN_PROGRESS_BAR - LEN_RIGHT_MARGIN ) def begin_new_line( self, report: Union[CollectReport, TestReport], print_filename: bool ) -> None: path = self.report_key(report) self.current_line_num += 1 if len(report.fspath) > self.get_max_column_for_test_status() - 5: fspath = ( "..." + report.fspath[-(self.get_max_column_for_test_status() - 5 - 5) :] ) else: fspath = report.fspath basename = os.path.basename(fspath) if print_filename: if self.showlongtestinfo: test_location = report.location[0] test_name = report.location[2] else: test_location = fspath[0 : -len(basename)] test_name = fspath[-len(basename) :] if test_location: pass # only replace if test_location is not empty, if it is, # test_name contains the filename # FIXME: This doesn't work. # test_name = test_name.replace('.', '::') self.current_lines[path] = ( " " + colored(test_location, THEME.path) + ("::" if self.verbosity > 0 else "") + colored(test_name, THEME.name) + " " ) else: self.current_lines[path] = " " * (2 + len(fspath)) self.current_line_nums[path] = self.current_line_num self.write("\r\n") def reached_last_column_for_test_status( self, report: Union[CollectReport, TestReport] ) -> bool: len_line = real_string_length(self.current_lines[self.report_key(report)]) return len_line >= self.get_max_column_for_test_status() def pytest_runtest_logstart(self, nodeid, location) -> None: # Prevent locationline from being printed since we already # show the module_name & in verbose mode the test name. pass def pytest_runtest_logfinish(self, nodeid: str) -> None: # prevent the default implementation to try to show # pytest's default progress pass def report_key(self, report: Union[CollectReport, TestReport]) -> Any: """Returns a key to identify which line the report should write to.""" return ( (report.location or "") if self.showlongtestinfo else (report.fspath or "") ) def pytest_runtest_logreport(self, report: TestReport) -> None: global LEN_PROGRESS_BAR_SETTING, LEN_PROGRESS_BAR res = pytest_report_teststatus(report=report) assert res cat, letter, word = res self.stats.setdefault(cat, []).append(report) if not LEN_PROGRESS_BAR: if LEN_PROGRESS_BAR_SETTING.endswith("%"): LEN_PROGRESS_BAR = ( self._tw.fullwidth * int(LEN_PROGRESS_BAR_SETTING[:-1]) // 100 ) else: LEN_PROGRESS_BAR = int(LEN_PROGRESS_BAR_SETTING) self.reports.append(report) if report.outcome == "failed": print("") self.print_failure(report) # Ignore other reports or it will cause duplicated letters if report.when == "teardown": self.tests_taken += 1 self.insert_progress(report) path = os.path.join(os.getcwd(), report.location[0]) if report.when == "call" or report.skipped: path = self.report_key(report) if path not in self.current_line_nums: self.begin_new_line(report, print_filename=True) elif self.reached_last_column_for_test_status(report): # Print filename if another line was inserted in-between print_filename = ( self.current_line_nums[self.report_key(report)] != self.current_line_num ) self.begin_new_line(report, print_filename) self.current_lines[path] = self.current_lines[path] + letter block = int( float(self.tests_taken) * LEN_PROGRESS_BAR / self.tests_count if self.tests_count else 0 ) if report.failed: if not self.progress_blocks or self.progress_blocks[-1][0] != block: self.progress_blocks.append([block, False]) elif self.progress_blocks and self.progress_blocks[-1][0] == block: self.progress_blocks[-1][1] = False else: if not self.progress_blocks or self.progress_blocks[-1][0] != block: self.progress_blocks.append([block, True]) if not letter and not word: return if self.verbosity > 0: markup = {"red": True} if isinstance(word, tuple): word, markup = word else: if report.passed: markup = {"green": True} elif report.skipped: markup = {"yellow": True} elif hasattr(report, "rerun") and isinstance(report.rerun, int): markup = {"blue": True} line = self._locationline(str(report.fspath), *report.location) if hasattr(report, "node"): self._tw.write("\r\n") self.current_line_num += 1 if hasattr(report, "node"): self._tw.write(f"[{report.node.gateway.id}] ") self._tw.write(word, **markup) self._tw.write(" " + line) self.currentfspath = -2 def count(self, key: str, when: tuple = ("call",)) -> int: value = self.stats.get(key) if value: return len([x for x in value if not hasattr(x, "when") or x.when in when]) return 0 def summary_stats(self) -> None: session_duration = time.time() - self._sessionstarttime print(f"\nResults ({format_session_duration(session_duration)}):") if self.count("passed") > 0: self.write_line( colored(" % 5d passed" % self.count("passed"), THEME.success) ) if self.count("xpassed") > 0: self.write_line( colored(" % 5d xpassed" % self.count("xpassed"), THEME.xpassed) ) if self.count("failed", when=("call",)) > 0: self.write_line( colored( " % 5d failed" % self.count("failed", when=("call",)), THEME.fail, ) ) for report in self.stats["failed"]: if report.when != "call": continue if self.config.option.tb_summary: crashline = self._get_decoded_crashline(report) else: path = os.path.dirname(report.location[0]) name = os.path.basename(report.location[0]) lineno = self._get_lineno_from_report(report) crashline = "{}{}{}:{} {}".format( colored(path, THEME.path), "/" if path else "", colored(name, THEME.name), lineno if lineno else "?", colored(report.location[2], THEME.fail), ) self.write_line(f" - {crashline}") if self.count("failed", when=("setup", "teardown")) > 0: self.write_line( colored( " % 5d error" % (self.count("failed", when=("setup", "teardown"))), THEME.error, ) ) if self.count("xfailed") > 0: self.write_line( colored(" % 5d xfailed" % self.count("xfailed"), THEME.xfailed) ) if self.count("skipped", when=("call", "setup", "teardown")) > 0: self.write_line( colored( " % 5d skipped" % (self.count("skipped", when=("call", "setup", "teardown"))), THEME.skipped, ) ) if self.count("rerun") > 0: self.write_line(colored(" % 5d rerun" % self.count("rerun"), THEME.rerun)) if self.count("deselected") > 0: self.write_line( colored(" % 5d deselected" % self.count("deselected"), THEME.warning) ) def _get_decoded_crashline(self, report: CollectReport) -> str: crashline = self._getcrashline(report) if hasattr(crashline, "decode"): encoding = locale.getpreferredencoding() try: crashline = crashline.decode(encoding) except UnicodeDecodeError: encoding = "utf-8" crashline = crashline.decode(encoding, errors="replace") return crashline def _get_lineno_from_report(self, report: CollectReport) -> int: # Doctest failures in pytest>3.10 are stored in # reprlocation_lines, a list of (ReprFileLocation, lines) try: location, lines = report.longrepr.reprlocation_lines[0] # type: ignore return location.lineno except AttributeError: pass # Doctest failure reports have lineno=None at least up to # pytest==3.0.7, but it is available via longrepr object. try: return report.longrepr.reprlocation.lineno # type: ignore except AttributeError: lineno = report.location[1] if lineno is not None: lineno += 1 return lineno def summary_failures(self) -> None: # Prevent failure summary from being shown since we already # show the failure instantly after failure has occurred. pass def summary_errors(self) -> None: # Prevent error summary from being shown since we already # show the error instantly after error has occurred. pass def print_failure(self, report: Union[CollectReport, TestReport]) -> None: # https://github.com/Frozenball/pytest-sugar/issues/34 if hasattr(report, "wasxfail"): return if self.config.option.tbstyle != "no": if self.config.option.tbstyle == "line": line = self._getcrashline(report) self.write_line(line) else: msg = self._getfailureheadline(report) # "when" was unset before pytest 4.2 for collection errors. when = getattr(report, "when", "collect") if when == "collect": msg = "ERROR collecting " + msg elif when == "setup": msg = "ERROR at setup of " + msg elif when == "teardown": msg = "ERROR at teardown of " + msg self.write_line("") self.write_sep("―", msg) self._outrep_summary(report) self.reset_tracked_lines() ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1706812216.833058 pytest-sugar-1.0.0/setup.cfg0000644000175100001770000000004614556761471015425 0ustar00runnerdocker[egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1706812202.0 pytest-sugar-1.0.0/setup.py0000644000175100001770000000454114556761452015321 0ustar00runnerdockerimport codecs from setuptools import setup # Copied from (and hacked): # https://github.com/pypa/virtualenv/blob/develop/setup.py#L42 def get_version(filename): import os import re here = os.path.dirname(os.path.abspath(__file__)) f = codecs.open(os.path.join(here, filename), encoding="utf-8") version_file = f.read() f.close() version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M) if version_match: return version_match.group(1) raise RuntimeError("Unable to find version string.") setup( name="pytest-sugar", description=( "pytest-sugar is a plugin for pytest that changes the default" " look and feel of pytest (e.g. progressbar, show tests that" " fail instantly)." ), long_description=codecs.open("README.md", encoding="utf-8").read(), long_description_content_type="text/markdown", version=get_version("pytest_sugar.py"), url="https://github.com/Teemu/pytest-sugar", project_urls={ "Issue Tracker": "https://github.com/Teemu/pytest-sugar/issues", }, license="BSD", author="Teemu, Janne Vanhala and others", author_email="orkkiolento@gmail.com, janne.vanhala@gmail.com", py_modules=["pytest_sugar"], entry_points={"pytest11": ["sugar = pytest_sugar"]}, zip_safe=False, include_package_data=True, platforms="any", install_requires=["pytest>=6.2.0", "termcolor>=2.1.0", "packaging>=21.3"], extras_require={ "dev": [ "black", "flake8", "pre-commit", ] }, classifiers=[ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: POSIX", "Operating System :: Microsoft :: Windows", "Operating System :: MacOS :: MacOS X", "Topic :: Software Development :: Testing", "Topic :: Software Development :: Libraries", "Topic :: Utilities", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation :: PyPy", ], ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1706812202.0 pytest-sugar-1.0.0/test_sugar.py0000644000175100001770000003622214556761452016342 0ustar00runnerdockerimport re import pytest from pytest_sugar import strip_colors pytest_plugins = "pytester" def get_counts(stdout): output = strip_colors(stdout) def _get(x): m = re.search(r"\d %s" % x, output) if m: return m.group()[0] return "n/a" return { x: _get(x) for x in ( "passed", "xpassed", "failed", "xfailed", "deselected", "error", "rerun", "skipped", ) } def assert_count(testdir, *args): """Assert that n passed, n failed, ... matches""" without_plugin = testdir.runpytest("-p", "no:sugar", *args).stdout.str() with_plugin = testdir.runpytest("--force-sugar", *args).stdout.str() count_without = get_counts(without_plugin) count_with = get_counts(with_plugin) assert count_without == count_with, ( "When running test with and without plugin, " "the resulting output differs.\n\n" "Without plugin: %s\n" "With plugin: %s\n" % ( ", ".join(f"{v} {k}" for k, v in count_without.items()), ", ".join(f"{v} {k}" for k, v in count_with.items()), ) ) class TestTerminalReporter: def test_new_summary(self, testdir): testdir.makepyfile( """ import pytest def test_sample(): assert False """ ) output = testdir.runpytest("--force-sugar").stdout.str() assert "test_new_summary.py:3 test_sample" in strip_colors(output) def test_old_summary(self, testdir): testdir.makepyfile( """ import pytest def test_sample(): assert False """ ) output = testdir.runpytest("--force-sugar", "--old-summary").stdout.str() assert "test_old_summary.py:4: assert False" in strip_colors(output) def test_xfail_true(self, testdir): testdir.makepyfile( """ import pytest @pytest.mark.xfail def test_sample(): assert True """ ) assert_count(testdir) def test_xfail_false(self, testdir): testdir.makepyfile( """ import pytest @pytest.mark.xfail def test_sample(): assert False """ ) assert_count(testdir) def test_report_header(self, testdir): testdir.makeconftest( """ def pytest_report_header(startdir): pass """ ) testdir.makepyfile( """ def test(): pass """ ) result = testdir.runpytest("--force-sugar") assert result.ret == 0, result.stderr.str() def test_xfail_strict_true(self, testdir): testdir.makepyfile( """ import pytest @pytest.mark.xfail(strict=True) def test_sample(): assert True """ ) assert_count(testdir) def test_xfail_strict_false(self, testdir): testdir.makepyfile( """ import pytest @pytest.mark.xfail(strict=True) def test_sample(): assert False """ ) assert_count(testdir) def test_xpass_true(self, testdir): testdir.makepyfile( """ import pytest @pytest.mark.xpass def test_sample(): assert True """ ) assert_count(testdir) def test_xpass_false(self, testdir): testdir.makepyfile( """ import pytest @pytest.mark.xpass def test_sample(): assert False """ ) assert_count(testdir) def test_flaky_test(self, testdir): pytest.importorskip("pytest_rerunfailures") testdir.makepyfile( """ import pytest COUNT = 0 @pytest.mark.flaky(reruns=10) def test_flaky_test(): global COUNT COUNT += 1 assert COUNT >= 7 """ ) assert_count(testdir) def test_xpass_strict(self, testdir): testdir.makepyfile( """ import pytest @pytest.mark.xfail(strict=True) def test_xpass(): assert True """ ) result = testdir.runpytest("--force-sugar") result.stdout.fnmatch_lines( [ "*test_xpass*", "*XPASS(strict)*", "*1 failed*", ] ) def test_teardown_errors(self, testdir): testdir.makepyfile( """ import pytest @pytest.yield_fixture def fixt(): yield raise Exception def test_foo(fixt): pass """ ) assert_count(testdir) result = testdir.runpytest("--force-sugar") result.stdout.fnmatch_lines( ["*ERROR at teardown of test_foo*", "*1 passed*", "*1 error*"] ) def test_skipping_tests(self, testdir): testdir.makepyfile( """ import pytest @pytest.mark.skipif(True, reason='This must be skipped.') def test_skip_this_if(): assert True """ ) assert_count(testdir) def test_deselecting_tests(self, testdir): testdir.makepyfile( """ import pytest @pytest.mark.example def test_func(): assert True def test_should_be(): assert False """ ) assert_count(testdir) def test_item_count_after_pytest_collection_modifyitems(self, testdir): testdir.makeconftest( """ import pytest @pytest.hookimpl(hookwrapper=True, tryfirst=True) def pytest_collection_modifyitems(config, items): yield items[:] = [x for x in items if x.name == 'test_one'] """ ) testdir.makepyfile( """ def test_one(): print('test_one_passed') def test_ignored(): assert 0 """ ) result = testdir.runpytest("-s") result.stdout.fnmatch_lines( [ "*test_one_passed*", "*100%*", ] ) assert result.ret == 0 def test_fail(self, testdir): testdir.makepyfile( """ import pytest def test_func(): assert 0 """ ) result = testdir.runpytest("--force-sugar") result.stdout.fnmatch_lines( [ "* test_func *", " def test_func():", "> assert 0", "E assert 0", ] ) def test_fail_unicode_crashline(self, testdir): testdir.makepyfile( """ # -*- coding: utf-8 -*- import pytest def test_func(): assert b'hello' == b'Bj\\xc3\\xb6rk Gu\\xc3\\xb0mundsd' """ ) result = testdir.runpytest("--force-sugar") result.stdout.fnmatch_lines( [ "* test_func *", " def test_func():", "> assert * == *", "E AssertionError: assert * == *", ] ) def test_fail_in_fixture_and_test(self, testdir): testdir.makepyfile( """ import pytest def test_func(): assert False def test_func2(): assert False @pytest.fixture def failure(): return 3/0 def test_lol(failure): assert True """ ) assert_count(testdir) output = strip_colors(testdir.runpytest("--force-sugar").stdout.str()) assert output.count(" -") == 2 def test_fail_fail(self, testdir): testdir.makepyfile( """ import pytest def test_func(): assert 0 def test_func2(): assert 0 """ ) assert_count(testdir) result = testdir.runpytest("--force-sugar") result.stdout.fnmatch_lines( [ "* test_func *", " def test_func():", "> assert 0", "E assert 0", "* test_func2 *", " def test_func2():", "> assert 0", "E assert 0", ] ) def test_error_in_setup_then_pass(self, testdir): testdir.makepyfile( """ def setup_function(function): print ("setup func") if function is test_nada: assert 0 def test_nada(): pass def test_zip(): pass """ ) assert_count(testdir) result = testdir.runpytest("--force-sugar") result.stdout.fnmatch_lines( [ "*ERROR at setup of test_nada*", "", "function = *assert 0*", "E*assert 0*", "test_error_in_teardown_then_pass.py:4: AssertionError", "*Captured stdout teardown*", "teardown func", "*2 passed*", ] ) assert result.ret != 0 def test_collect_error(self, testdir): testdir.makepyfile("""raise ValueError(0)""") assert_count(testdir) result = testdir.runpytest("--force-sugar") result.stdout.fnmatch_lines( [ "*ERROR collecting test_collect_error.py*", "test_collect_error.py:1: in ", " raise ValueError(0)", "E ValueError: 0", ] ) def test_verbose(self, testdir): testdir.makepyfile( """ import pytest def test_true(): assert True def test_true2(): assert True def test_false(): assert False @pytest.mark.skip def test_skip(): assert False @pytest.mark.xpass def test_xpass(): assert True @pytest.mark.xfail def test_xfail(): assert True """ ) assert_count(testdir, "--verbose") def test_verbose_has_double_colon(self, testdir): testdir.makepyfile( """ def test_true(): assert True """ ) output = testdir.runpytest("--force-sugar", "--verbose").stdout.str() assert "test_verbose_has_double_colon.py::test_true" in strip_colors(output) # def test_verbose_has_double_colon_with_class(self, testdir): # testdir.makepyfile( # """ # class TestTrue: # def test_true(self): # assert True # """ # ) # output = testdir.runpytest( # '--force-sugar', '--verbose' # ).stdout.str() # test_name = ( # 'test_verbose_has_double_colon_with_class.py::TestTrue::test_true') # assert test_name in strip_colors(output) # def test_not_verbose_no_double_colon_filename(self, testdir): # testdir.makepyfile( # """ # class TestTrue: # def test_true(self): # assert True # """ # ) # output = testdir.runpytest( # '--force-sugar' # ).stdout.str() # test_name = 'test_not_verbose_no_double_colon_filename.py' # assert test_name in strip_colors(output) def test_xdist(self, testdir): pytest.importorskip("xdist") testdir.makepyfile( """ def test_nada(): pass def test_zip(): pass """ ) result = testdir.runpytest("--force-sugar", "-n2") assert result.ret == 0, result.stderr.str() def test_xdist_verbose(self, testdir): pytest.importorskip("xdist") testdir.makepyfile( """ def test_nada(): pass def test_zip(): pass """ ) result = testdir.runpytest("--force-sugar", "-n2", "-v") assert result.ret == 0, result.stderr.str() def test_doctest(self, testdir): """Test doctest-modules""" testdir.makepyfile( """ class ToTest(): @property def doctest(self): \"\"\" >>> Invalid doctest \"\"\" """ ) result = testdir.runpytest("--force-sugar", "--doctest-modules") assert result.ret == 1, result.stderr.str() def test_doctest_lineno(self, testdir): """Test location reported for doctest-modules""" testdir.makepyfile( """ def foobar(): ''' >>> foobar() ''' raise NotImplementedError """ ) result = testdir.runpytest("--force-sugar", "--doctest-modules") assert result.ret == 1, result.stderr.str() result.stdout.fnmatch_lines( [ "UNEXPECTED EXCEPTION: NotImplementedError()", "*test_doctest_lineno.py:3: UnexpectedException", "Results*:", "*-*test_doctest_lineno.py*:3*", ] ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1706812202.0 pytest-sugar-1.0.0/tox.ini0000644000175100001770000000123014556761452015112 0ustar00runnerdocker[tox] minversion = 3.14.0 envlist = py{38,312,py}-pytest_latest-supported-xdist qa requires = virtualenv>=20.0.31 [testenv] install_command = python -m pip install --use-feature=fast-deps {opts} {packages} deps = pytest62: pytest~=6.2.5 pytest_latest: pytest termcolor>=1.1.0 supported-xdist: pytest-xdist supported-xdist: pytest-forked pytest-cov commands = pytest --cov --cov-config=.coveragerc {posargs:test_sugar.py} [testenv:qa] deps = flake8 black commands = flake8 {posargs:conftest.py pytest_sugar.py setup.py test_sugar.py} black --check {posargs:conftest.py pytest_sugar.py setup.py test_sugar.py}