pytest-sugar-0.9.4/0000755000076500000240000000000013700667725014452 5ustar teemustaff00000000000000pytest-sugar-0.9.4/PKG-INFO0000644000076500000240000000527213700667725015555 0ustar teemustaff00000000000000Metadata-Version: 1.1 Name: pytest-sugar Version: 0.9.4 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: http://pivotfinland.com/pytest-sugar/ Author: Teemu, Janne Vanhala and others Author-email: orkkiolento@gmail.com, janne.vanhala@gmail.com License: BSD Description: # pytest-sugar [![Build status](https://travis-ci.org/Teemu/pytest-sugar.svg?branch=master)](https://travis-ci.org/Teemu/pytest-sugar) [![Pypi version](https://img.shields.io/pypi/v/pytest-sugar.svg)](https://pypi.org/project/pytest-sugar/) pytest-sugar is a plugin for [py.test](http://pytest.org) that shows failures and errors instantly and shows a progress bar. ![Demo](http://pivotfinland.com/pytest-sugar/img/video.gif) ## Requirements You will need the following prerequisites in order to use pytest-sugar: - Python 2.7, 3.4 or newer - pytest 2.9.0 or newer - pytest-xdist 1.14 or above if you want the progress bar to work while running tests in parallel ## Installation To install pytest-sugar: pip install pytest-sugar Then run your tests with: py.test If you would like more detailed output (one test per line), then you may use the verbose option: py.test --verbose If you would like to run tests without pytest-sugar, use: py.test -p no:sugar ## 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. Platform: any Classifier: Development Status :: 3 - Alpha 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 :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: Implementation :: PyPy pytest-sugar-0.9.4/test_sugar.py0000644000076500000240000003557513700667345017221 0ustar teemustaff00000000000000# -*- coding: utf-8 -*- import pytest import re 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] else: 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('%s %s' % (v, k) for k, v in count_without.items()), ", ".join('%s %s' % (v, k) for k, v in count_with.items()), ) ) class TestTerminalReporter(object): 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(object): @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*', ]) pytest-sugar-0.9.4/LICENSE0000644000076500000240000000272213651305735015455 0ustar teemustaff00000000000000Copyright (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. pytest-sugar-0.9.4/pytest_sugar.egg-info/0000755000076500000240000000000013700667725020675 5ustar teemustaff00000000000000pytest-sugar-0.9.4/pytest_sugar.egg-info/PKG-INFO0000644000076500000240000000527213700667725022000 0ustar teemustaff00000000000000Metadata-Version: 1.1 Name: pytest-sugar Version: 0.9.4 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: http://pivotfinland.com/pytest-sugar/ Author: Teemu, Janne Vanhala and others Author-email: orkkiolento@gmail.com, janne.vanhala@gmail.com License: BSD Description: # pytest-sugar [![Build status](https://travis-ci.org/Teemu/pytest-sugar.svg?branch=master)](https://travis-ci.org/Teemu/pytest-sugar) [![Pypi version](https://img.shields.io/pypi/v/pytest-sugar.svg)](https://pypi.org/project/pytest-sugar/) pytest-sugar is a plugin for [py.test](http://pytest.org) that shows failures and errors instantly and shows a progress bar. ![Demo](http://pivotfinland.com/pytest-sugar/img/video.gif) ## Requirements You will need the following prerequisites in order to use pytest-sugar: - Python 2.7, 3.4 or newer - pytest 2.9.0 or newer - pytest-xdist 1.14 or above if you want the progress bar to work while running tests in parallel ## Installation To install pytest-sugar: pip install pytest-sugar Then run your tests with: py.test If you would like more detailed output (one test per line), then you may use the verbose option: py.test --verbose If you would like to run tests without pytest-sugar, use: py.test -p no:sugar ## 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. Platform: any Classifier: Development Status :: 3 - Alpha 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 :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: Implementation :: PyPy pytest-sugar-0.9.4/pytest_sugar.egg-info/not-zip-safe0000644000076500000240000000000113651306210023103 0ustar teemustaff00000000000000 pytest-sugar-0.9.4/pytest_sugar.egg-info/SOURCES.txt0000644000076500000240000000056013700667725022562 0ustar teemustaff00000000000000CHANGES.rst CONTRIBUTORS.rst LICENSE MANIFEST.in README.md pytest_sugar.py setup.cfg 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.txtpytest-sugar-0.9.4/pytest_sugar.egg-info/entry_points.txt0000644000076500000240000000004113700667725024166 0ustar teemustaff00000000000000[pytest11] sugar = pytest_sugar pytest-sugar-0.9.4/pytest_sugar.egg-info/requires.txt0000644000076500000240000000005513700667725023275 0ustar teemustaff00000000000000pytest>=2.9 termcolor>=1.1.0 packaging>=14.1 pytest-sugar-0.9.4/pytest_sugar.egg-info/top_level.txt0000644000076500000240000000001513700667725023423 0ustar teemustaff00000000000000pytest_sugar pytest-sugar-0.9.4/pytest_sugar.egg-info/dependency_links.txt0000644000076500000240000000000113700667725024743 0ustar teemustaff00000000000000 pytest-sugar-0.9.4/MANIFEST.in0000644000076500000240000000013413651305735016201 0ustar teemustaff00000000000000include README.md CONTRIBUTORS.rst CHANGES.rst LICENSE include test_sugar.py include tox.inipytest-sugar-0.9.4/README.md0000644000076500000240000000226013651305735015724 0ustar teemustaff00000000000000# pytest-sugar [![Build status](https://travis-ci.org/Teemu/pytest-sugar.svg?branch=master)](https://travis-ci.org/Teemu/pytest-sugar) [![Pypi version](https://img.shields.io/pypi/v/pytest-sugar.svg)](https://pypi.org/project/pytest-sugar/) pytest-sugar is a plugin for [py.test](http://pytest.org) that shows failures and errors instantly and shows a progress bar. ![Demo](http://pivotfinland.com/pytest-sugar/img/video.gif) ## Requirements You will need the following prerequisites in order to use pytest-sugar: - Python 2.7, 3.4 or newer - pytest 2.9.0 or newer - pytest-xdist 1.14 or above if you want the progress bar to work while running tests in parallel ## Installation To install pytest-sugar: pip install pytest-sugar Then run your tests with: py.test If you would like more detailed output (one test per line), then you may use the verbose option: py.test --verbose If you would like to run tests without pytest-sugar, use: py.test -p no:sugar ## 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. pytest-sugar-0.9.4/setup.py0000644000076500000240000000431113651305735016156 0ustar teemustaff00000000000000from setuptools import setup import codecs # 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='http://pivotfinland.com/pytest-sugar/', 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>=2.9', 'termcolor>=1.1.0', 'packaging>=14.1'], classifiers=[ 'Development Status :: 3 - Alpha', '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 :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: Implementation :: PyPy', ] ) pytest-sugar-0.9.4/pytest_sugar.py0000644000076500000240000005316713700667450017564 0ustar teemustaff00000000000000# -*- coding: utf-8 -*- """ pytest_sugar ~~~~~~~~~~~~ py.test is a plugin for py.test that changes the default look and feel of py.test (e.g. progressbar, show tests that fail instantly). :copyright: see LICENSE for details :license: BSD, see LICENSE for more details. """ from __future__ import unicode_literals import locale import os import re import sys from packaging.version import parse try: from configparser import ConfigParser except ImportError: from ConfigParser import ConfigParser from termcolor import colored import py import pytest from _pytest.terminal import TerminalReporter __version__ = '0.9.4' LEN_RIGHT_MARGIN = 0 LEN_PROGRESS_PERCENTAGE = 5 LEN_PROGRESS_BAR_SETTING = '10' LEN_PROGRESS_BAR = None THEME = { 'header': 'magenta', 'skipped': 'blue', 'success': 'green', 'warning': 'yellow', 'fail': 'red', 'error': 'red', 'xfailed': 'green', 'xpassed': 'red', 'progressbar': 'green', 'progressbar_fail': 'red', 'progressbar_background': 'grey', 'path': 'cyan', 'name': None, 'symbol_passed': '✓', 'symbol_skipped': 's', 'symbol_failed': '⨯', 'symbol_failed_not_call': 'ₓ', 'symbol_xfailed_skipped': 'x', 'symbol_xfailed_failed': 'X', 'symbol_unknown': '?', 'unknown': 'blue', 'symbol_rerun': 'R', 'rerun': 'blue', } PROGRESS_BAR_BLOCKS = [ ' ', '▏', '▎', '▎', '▍', '▍', '▌', '▌', '▋', '▋', '▊', '▊', '▉', '▉', '█', ] def flatten(seq): for x in seq: if isinstance(x, (list, tuple)): for y in flatten(x): yield y else: yield x def pytest_runtestloop(session): reporter = session.config.pluginmanager.getplugin('terminalreporter') if reporter: reporter.tests_count = len(session.items) class DeferredXdistPlugin(object): def pytest_xdist_node_collection_finished(self, node, ids): terminal_reporter = node.config.pluginmanager.getplugin( 'terminalreporter' ) if terminal_reporter: terminal_reporter.tests_count = len(ids) def pytest_deselected(items): """ 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): 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): global LEN_PROGRESS_BAR_SETTING config = ConfigParser() config.read([ 'pytest-sugar.conf', os.path.expanduser('~/.pytest-sugar.conf') ]) for key in THEME: if not config.has_option('theme', key): continue value = config.get("theme", key) value = value.lower() if value in ('', 'none'): value = None THEME[key] = value if config.has_option('sugar', 'progressbar_length'): LEN_PROGRESS_BAR_SETTING = config.get('sugar', 'progressbar_length') def strip_colors(text): ansi_escape = re.compile(r'\x1b[^m]*m') stripped = ansi_escape.sub('', text) return stripped def real_string_length(string): return len(strip_colors(string)) IS_SUGAR_ENABLED = False @pytest.mark.trylast def pytest_configure(config): 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 except ImportError: pass else: from distutils.version import LooseVersion xdist_version = LooseVersion(xdist.__version__) if xdist_version >= LooseVersion('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): if not IS_SUGAR_ENABLED: return 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" elif 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, reporter): 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): self.current_lines = {} self.current_line_nums = {} self.current_line_num = 0 def report_collect(self, final=False): pass def pytest_collectreport(self, report): 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): self._session = session self._sessionstarttime = py.std.time.time() 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 ) lines = self.config.hook.pytest_report_header( config=self.config, startdir=self.startdir) lines.reverse() for line in flatten(lines): self.write_line(line) def write_fspath_result(self, fspath, res): return def insert_progress(self, report): def get_progress_bar(): 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, rel_line_num): # Move cursor up rel_line_num lines if rel_line_num > 0: self.write("\033[%dA" % rel_line_num) # Overwrite the line self.write("\r%s" % 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): return ( self._tw.fullwidth - LEN_PROGRESS_PERCENTAGE - LEN_PROGRESS_BAR - LEN_RIGHT_MARGIN ) def begin_new_line(self, report, print_filename): 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): 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): # Prevent locationline from being printed since we already # show the module_name & in verbose mode the test name. pass def pytest_runtest_logfinish(self): # prevent the default implementation to try to show # pytest's default progress pass def report_key(self, report): """Returns a key to identify which line the report should write to.""" return report.location if self.showlongtestinfo else report.fspath def pytest_runtest_logreport(self, report): global LEN_PROGRESS_BAR_SETTING, LEN_PROGRESS_BAR res = pytest_report_teststatus(report=report) 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: if isinstance(word, tuple): word, markup = word else: if report.passed: markup = {'green': True} elif report.failed: markup = {'red': True} elif report.skipped: markup = {'yellow': 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("[%s] " % report.node.gateway.id) self._tw.write(word, **markup) self._tw.write(" " + line) self.currentfspath = -2 def count(self, key, when=('call',)): if self.stats.get(key): return len([ x for x in self.stats.get(key) if not hasattr(x, 'when') or x.when in when ]) else: return 0 def summary_stats(self): session_duration = py.std.time.time() - self._sessionstarttime print("\nResults (%.2fs):" % round(session_duration, 2)) 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 = '%s%s%s:%s %s' % ( colored(path, THEME['path']), '/' if path else '', colored(name, THEME['name']), lineno if lineno else '?', colored(report.location[2], THEME['fail']) ) self.write_line(" - %s" % 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): 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): # Doctest failures in pytest>3.10 are stored in # reprlocation_lines, a list of (ReprFileLocation, lines) try: location, lines = report.longrepr.reprlocation_lines[0] 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 except AttributeError: lineno = report.location[1] if lineno is not None: lineno += 1 return lineno def summary_failures(self): # Prevent failure summary from being shown since we already # show the failure instantly after failure has occurred. pass def summary_errors(self): # Prevent error summary from being shown since we already # show the error instantly after error has occurred. pass def print_failure(self, report): # 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() # On older version of Pytest, allow default progress if parse(pytest.__version__) <= parse('3.4'): # pragma: no cover del SugarTerminalReporter.pytest_runtest_logfinish pytest-sugar-0.9.4/tox.ini0000644000076500000240000000227313651305735015764 0ustar teemustaff00000000000000[tox] envlist = py{27,34,35,36,37,py}-pytest39-{supported,unsupported}-xdist qa [testenv] deps = pytest-cov pytest36: pytest>=3.6,<3.7 pytest37: pytest>=3.7,<3.8 pytest38: pytest>=3.8,<3.9 pytest39: pytest>=3.9,<3.10 pytest310: pytest>=3.10,<3.11 pytest40: pytest>=4.0,<4.1 pytest41: pytest>=4.1,<4.2 pytest42: pytest>=4.2,<4.3 pytest43: pytest>=4.3,<4.4 pytest44: pytest>=4.4,<4.5 pytest45: pytest>=4.5,<4.6 pytest46: pytest>=4.6,<4.7 pytest54: pytest>=5.4,<5.5 termcolor>=1.1.0 supported-xdist: pytest-xdist{env:_TOX_SUGAR_XDIST_VERSION:>=1.14} unsupported-xdist: pytest-xdist<1.14 rerunfailures: pytest-rerunfailures pinned-attrs: attrs<19.2 setenv = # pytest-xdist >= 1.28 requires pytest 4.4 pytest{36,37,38,39,310,40,41,42,43}: _TOX_SUGAR_XDIST_VERSION=>=1.14,<1.28 commands = pytest --cov --cov-config=.coveragerc {posargs:test_sugar.py} # Oldest supported (testable) version. [testenv:pytest30] deps = pytest>=3.0,<3.1 pytest-xdist<1.25 pytest-forked<0.3 pytest-cov<2.6.1 [testenv:qa] deps = flake8 commands = flake8 {posargs:conftest.py pytest_sugar.py setup.py test_sugar.py} pytest-sugar-0.9.4/CONTRIBUTORS.rst0000644000076500000240000000027713651305735017142 0ustar teemustaff00000000000000The following people have contributed to pytest-sugar: * Janne Vanhala * Teemu * Marc Abramowitz * Yizhe Tang * Mahdi Yusuf * dscerri * Mounier Florian * Balthazar Rouberol * Michael Howitz pytest-sugar-0.9.4/setup.cfg0000644000076500000240000000013013700667725016265 0ustar teemustaff00000000000000[bdist_wheel] universal = 1 [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 pytest-sugar-0.9.4/CHANGES.rst0000644000076500000240000000622313700667642016255 0ustar teemustaff00000000000000Changelog --------- A list of changes between each release. 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