././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1755951562.421687 pytest-sugar-1.1.1/0000755000175100002000000000000015052330712013547 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755951561.0 pytest-sugar-1.1.1/CHANGES.rst0000644000175100002000000001133315052330711015351 0ustar00runnerdockerChangelog --------- 1.1.1 - 2025-08-23 ^^^^^^^^^^^^^^^^^^ Adjust signature of SugarTerminalReporter to avoid conflicts with other pytest plugins Contributed by [Daniil](https://github.com/TolstochenkoDaniil) via [PR #297](https://github.com/Teemu/pytest-sugar/pull/297/) 1.1.0 - 2025-08-16 ^^^^^^^^^^^^^^^^^^ Add Playwright trace file detection and display support for failed tests. This enhancement automatically detects and displays Playwright trace.zip files with viewing commands when tests fail, making debugging easier for Playwright users. ![Playwright trace.zip](docs/images/playwright-trace-example.png) New command-line options: - `--sugar-trace-dir`: Configure the directory name for Playwright trace files (default: test-results) - `--sugar-no-trace`: Disable Playwright trace file detection and display Contributed by [kie](https://github.com/kiebak3r) via [PR #296](https://github.com/Teemu/pytest-sugar/pull/296/) 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=1755951541.0 pytest-sugar-1.1.1/CONTRIBUTORS.rst0000644000175100002000000000035315052330665016246 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=1755951541.0 pytest-sugar-1.1.1/LICENSE0000644000175100002000000000302515052330665014563 0ustar00runnerdockerBSD 3-Clause License Copyright (c) 2013, Teemu Copyright (c) 2013, Janne Vanhala Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. 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. 3. Neither the name of the copyright holder nor the names of its contributors may 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 OR CONTRIBUTORS 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=1755951541.0 pytest-sugar-1.1.1/MANIFEST.in0000644000175100002000000000013515052330665015313 0ustar00runnerdockerinclude README.md CONTRIBUTORS.rst CHANGES.rst LICENSE include test_sugar.py include tox.ini ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1755951562.421687 pytest-sugar-1.1.1/PKG-INFO0000644000175100002000000001165715052330712014656 0ustar00runnerdockerMetadata-Version: 2.1 Name: pytest-sugar Version: 1.1.1 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 :: 3.13 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 ## Usage pytest-sugar provides several command-line options to customize its output and behavior. These options enhance test reporting and Playwright trace integration: Show detailed test failures instead of one-line tracebacks. Use this if you want to see the full failure information instantly. --old-summary Force pytest-sugar output even if pytest doesn’t detect a real terminal. Useful when running tests in CI systems or other non-interactive environments. --force-sugar Specify the directory where Playwright trace files are stored. Defaults to Playwright default: "test-results" --sugar-trace-dir Disable Playwright trace file detection and output display. Use this if you want to turn off trace collection or display entirely. --sugar-no-trace ## 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=1755951541.0 pytest-sugar-1.1.1/README.md0000644000175100002000000000712015052330665015035 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 ## Usage pytest-sugar provides several command-line options to customize its output and behavior. These options enhance test reporting and Playwright trace integration: Show detailed test failures instead of one-line tracebacks. Use this if you want to see the full failure information instantly. --old-summary Force pytest-sugar output even if pytest doesn’t detect a real terminal. Useful when running tests in CI systems or other non-interactive environments. --force-sugar Specify the directory where Playwright trace files are stored. Defaults to Playwright default: "test-results" --sugar-trace-dir Disable Playwright trace file detection and output display. Use this if you want to turn off trace collection or display entirely. --sugar-no-trace ## 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=1755951561.0 pytest-sugar-1.1.1/pyproject.toml0000644000175100002000000000255715052330711016473 0ustar00runnerdocker[tool.poetry] name = "pytest-sugar" version = "1.1.1" 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" [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=1755951562.421687 pytest-sugar-1.1.1/pytest_sugar.egg-info/0000755000175100002000000000000015052330712017772 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755951562.0 pytest-sugar-1.1.1/pytest_sugar.egg-info/PKG-INFO0000644000175100002000000001165715052330712021101 0ustar00runnerdockerMetadata-Version: 2.1 Name: pytest-sugar Version: 1.1.1 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 :: 3.13 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 ## Usage pytest-sugar provides several command-line options to customize its output and behavior. These options enhance test reporting and Playwright trace integration: Show detailed test failures instead of one-line tracebacks. Use this if you want to see the full failure information instantly. --old-summary Force pytest-sugar output even if pytest doesn’t detect a real terminal. Useful when running tests in CI systems or other non-interactive environments. --force-sugar Specify the directory where Playwright trace files are stored. Defaults to Playwright default: "test-results" --sugar-trace-dir Disable Playwright trace file detection and output display. Use this if you want to turn off trace collection or display entirely. --sugar-no-trace ## 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=1755951562.0 pytest-sugar-1.1.1/pytest_sugar.egg-info/SOURCES.txt0000644000175100002000000000056515052330712021664 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=1755951562.0 pytest-sugar-1.1.1/pytest_sugar.egg-info/dependency_links.txt0000644000175100002000000000000115052330712024040 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755951562.0 pytest-sugar-1.1.1/pytest_sugar.egg-info/entry_points.txt0000644000175100002000000000004115052330712023263 0ustar00runnerdocker[pytest11] sugar = pytest_sugar ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755951562.0 pytest-sugar-1.1.1/pytest_sugar.egg-info/not-zip-safe0000644000175100002000000000000115052330712022220 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755951562.0 pytest-sugar-1.1.1/pytest_sugar.egg-info/requires.txt0000644000175100002000000000007615052330712022375 0ustar00runnerdockerpytest>=6.2.0 termcolor>=2.1.0 [dev] black flake8 pre-commit ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755951562.0 pytest-sugar-1.1.1/pytest_sugar.egg-info/top_level.txt0000644000175100002000000000001515052330712022520 0ustar00runnerdockerpytest_sugar ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755951561.0 pytest-sugar-1.1.1/pytest_sugar.py0000644000175100002000000006403115052330711016655 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, TextIO, Tuple, Union import pytest from _pytest.config import Config 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.1.1" 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"), ) group._addoption( "--sugar-trace-dir", action="store", dest="sugar_trace_dir", default="test-results", help=("Directory name for Playwright trace files (default: test-results)"), ) group._addoption( "--sugar-no-trace", action="store_true", dest="sugar_no_trace", default=False, help=("Disable Playwright trace file detection and display"), ) 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 # noqa: F401 except ImportError: pass else: 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) 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): def __init__(self, config: Config, file: Union[TextIO, None] = None) -> None: TerminalReporter.__init__(self, config, file) 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 i, report in enumerate(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), ) # Add trace.zip path if it exists trace_path = self._find_playwright_trace(report) if trace_path: crashline += f"\n - 🎭 {trace_path}" 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 _find_playwright_trace(self, report: TestReport) -> Optional[str]: """ Finds the Playwright trace file associated with a specific test report. Identifies the location of the trace file by using the test report's node ID and configuration options. It allows users to locate and optionally view the Playwright trace for failed tests. If Playwright trace finding is specifically disabled, or the trace file does not exist for the given test, no output is returned. Parameters: report (TestReport): The test report containing details about the test execution, including the node ID. Returns: Optional[str]: A string containing command to view the Playwright trace or None if trace is not enabled, the file does not exist, or an exception occurs. """ # Check if trace finding is disabled if self.config.option.sugar_no_trace: return None try: # Extract test information from the report nodeid = report.nodeid # Handle Node ID conversion to trace name format trace_dir_name = self._convert_node_to_trace_name(nodeid) # Construct the expected trace directory path cwd = os.getcwd() trace_dir_name_from_config = self.config.option.sugar_trace_dir test_results_dir = os.path.join(cwd, trace_dir_name_from_config) trace_dir = os.path.join(test_results_dir, trace_dir_name) trace_file = os.path.join(trace_dir, "trace.zip") # Check if the trace file exists if os.path.exists(trace_file): # Provide the relative path and a command to view the trace trace_file_relative = os.path.relpath(trace_file, cwd).replace( "\\", "/" ) # Create a command to open the trace with Playwright for Python view_command = f"playwright show-trace {trace_file_relative}" # Display unzip command command_display = colored(view_command, THEME.warning) return command_display return None except (OSError, AttributeError): return None @staticmethod def _convert_node_to_trace_name(nodeid: str) -> str: # Convert the nodeid to the expected trace directory name trace_dir_name = ( nodeid.replace("/", "-") .replace("\\", "-") .replace("::", "-") .replace("[", "-") .replace("]", "") .replace("_", "-") .replace(".", "-") ) return trace_dir_name.lower() 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=1755951562.421687 pytest-sugar-1.1.1/setup.cfg0000644000175100002000000000004615052330712015370 0ustar00runnerdocker[egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755951541.0 pytest-sugar-1.1.1/setup.py0000644000175100002000000000460015052330665015270 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"], 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 :: 3.13", "Programming Language :: Python :: Implementation :: PyPy", ], ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1755951541.0 pytest-sugar-1.1.1/test_sugar.py0000644000175100002000000003731315052330665016317 0ustar00runnerdockerimport io import re import pytest from pytest_sugar import SugarTerminalReporter, 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_sugar_terminal_reporter_init_signature(self, pytestconfig): terminal_reporter = pytestconfig.pluginmanager.getplugin("terminalreporter") sugar_reporter = SugarTerminalReporter(terminal_reporter.config) assert sugar_reporter.config is terminal_reporter.config file_obj = io.StringIO() sugar_reporter = SugarTerminalReporter(terminal_reporter.config, file=file_obj) assert sugar_reporter.config is terminal_reporter.config assert sugar_reporter._tw._file is file_obj 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=1755951541.0 pytest-sugar-1.1.1/tox.ini0000644000175100002000000000123015052330665015065 0ustar00runnerdocker[tox] minversion = 3.14.0 envlist = py{38,313,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}