pytest-sugar-0.9.2/0000755000076500000240000000000013371122503014574 5ustar banskustaff00000000000000pytest-sugar-0.9.2/CHANGES.rst0000644000076500000240000000553513371121040016401 0ustar banskustaff00000000000000Changelog --------- A list of changes between each release. 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 pytest-sugar-0.9.2/CONTRIBUTORS.rst0000644000076500000240000000027713144340465017300 0ustar banskustaff00000000000000The 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.2/LICENSE0000644000076500000240000000272213144340465015613 0ustar banskustaff00000000000000Copyright (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.2/MANIFEST.in0000644000076500000240000000013413144340465016337 0ustar banskustaff00000000000000include README.md CONTRIBUTORS.rst CHANGES.rst LICENSE include test_sugar.py include tox.inipytest-sugar-0.9.2/PKG-INFO0000644000076500000240000000525513371122503015700 0ustar banskustaff00000000000000Metadata-Version: 2.1 Name: pytest-sugar Version: 0.9.2 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 [![](https://travis-ci.org/Frozenball/pytest-sugar.svg?branch=master)](https://travis-ci.org/Frozenball/pytest-sugar) ![](https://img.shields.io/pypi/v/pytest-sugar.svg) pytest-sugar is a plugin for [py.test](http://pytest.org) that shows failures and errors instantly and shows a progress bar. ![](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/Frozenball/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 Description-Content-Type: text/markdown pytest-sugar-0.9.2/README.md0000644000076500000240000000220313236327046016061 0ustar banskustaff00000000000000# pytest-sugar [![](https://travis-ci.org/Frozenball/pytest-sugar.svg?branch=master)](https://travis-ci.org/Frozenball/pytest-sugar) ![](https://img.shields.io/pypi/v/pytest-sugar.svg) pytest-sugar is a plugin for [py.test](http://pytest.org) that shows failures and errors instantly and shows a progress bar. ![](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/Frozenball/pytest-sugar/pull/49#issuecomment-146567670) for more details. pytest-sugar-0.9.2/pytest_sugar.egg-info/0000755000076500000240000000000013371122503021017 5ustar banskustaff00000000000000pytest-sugar-0.9.2/pytest_sugar.egg-info/PKG-INFO0000644000076500000240000000525513371122503022123 0ustar banskustaff00000000000000Metadata-Version: 2.1 Name: pytest-sugar Version: 0.9.2 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 [![](https://travis-ci.org/Frozenball/pytest-sugar.svg?branch=master)](https://travis-ci.org/Frozenball/pytest-sugar) ![](https://img.shields.io/pypi/v/pytest-sugar.svg) pytest-sugar is a plugin for [py.test](http://pytest.org) that shows failures and errors instantly and shows a progress bar. ![](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/Frozenball/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 Description-Content-Type: text/markdown pytest-sugar-0.9.2/pytest_sugar.egg-info/SOURCES.txt0000644000076500000240000000056013371122503022704 0ustar banskustaff00000000000000CHANGES.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.2/pytest_sugar.egg-info/dependency_links.txt0000644000076500000240000000000113371122503025065 0ustar banskustaff00000000000000 pytest-sugar-0.9.2/pytest_sugar.egg-info/entry_points.txt0000644000076500000240000000004113371122503024310 0ustar banskustaff00000000000000[pytest11] sugar = pytest_sugar pytest-sugar-0.9.2/pytest_sugar.egg-info/not-zip-safe0000644000076500000240000000000113144346065023256 0ustar banskustaff00000000000000 pytest-sugar-0.9.2/pytest_sugar.egg-info/requires.txt0000644000076500000240000000005513371122503023417 0ustar banskustaff00000000000000pytest>=2.9 termcolor>=1.1.0 packaging>=14.1 pytest-sugar-0.9.2/pytest_sugar.egg-info/top_level.txt0000644000076500000240000000001513371122503023545 0ustar banskustaff00000000000000pytest_sugar pytest-sugar-0.9.2/pytest_sugar.py0000644000076500000240000005233013371121433017703 0ustar banskustaff00000000000000# -*- 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.2' 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(l): for x in l: 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.writer = self._tw 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._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.writer.write("\033[%dA" % rel_line_num) # Overwrite the line self.writer.write("\r%s" % line) # Return cursor to original line if rel_line_num > 0: self.writer.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.writer.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]) # Doctest failure reports have lineno=None at least up to # pytest==3.0.7, but it is available via longrepr object. try: lineno = report.longrepr.reprlocation.lineno except AttributeError: lineno = report.location[1] if lineno is not None: lineno += 1 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 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) if not hasattr(report, 'when'): msg = "ERROR collecting " + msg elif report.when == "setup": msg = "ERROR at setup of " + msg elif report.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.2/setup.cfg0000644000076500000240000000010313371122503016407 0ustar banskustaff00000000000000[bdist_wheel] universal = 1 [egg_info] tag_build = tag_date = 0 pytest-sugar-0.9.2/setup.py0000644000076500000240000000431113371120554016311 0ustar banskustaff00000000000000from 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.2/test_sugar.py0000644000076500000240000003610213371122332017330 0ustar banskustaff00000000000000# -*- coding: utf-8 -*- import pytest import re from distutils.version import LooseVersion 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('-p no:sugar', '-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-module') assert result.ret == 1, result.stderr.str() @pytest.mark.skipif( LooseVersion(pytest.__version__) >= LooseVersion('3.5'), reason='Temporarily skipping until #134' ) 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-module') 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.2/tox.ini0000644000076500000240000000225113371120554016113 0ustar banskustaff00000000000000# Tox (http://tox.testrun.org/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. [tox] envlist = py{27,34,35,36,py}-pytest30-supported-xdist, py{27,35}-pytest30-unsupported-xdist, py{27,34,py}-pytest31-supported-xdist py{27,34,py}-pytest34-supported-xdist qa [testenv] passenv = CI TRAVIS_BUILD_ID TRAVIS TRAVIS_BRANCH TRAVIS_JOB_NUMBER TRAVIS_PULL_REQUEST TRAVIS_JOB_ID TRAVIS_REPO_SLUG TRAVIS_COMMIT deps = codecov>=1.4.0 pytest-cov pytest30: pytest>=3.0,<3.1 pytest31: pytest>=3.1,<3.2 pytest34: pytest>=3.4,<3.5 pytest37: pytest>=3.7,<3.8 pytest39: pytest>=3.9,<3.10 pytest310: pytest>=3.10,<3.11 termcolor>=1.1.0 supported-xdist: pytest-xdist>=1.14 unsupported-xdist: pytest-xdist<1.14 rerunfailures: pytest-rerunfailures commands = py.test --cov --cov-config=.coveragerc {posargs:test_sugar.py} codecov -e TOXENV [testenv:qa] deps = flake8 commands = flake8 {posargs:conftest.py pytest_sugar.py setup.py test_sugar.py}