2010-11-06 18:38:53 +08:00
|
|
|
""" terminal reporting of the full testing process.
|
2009-07-22 22:09:49 +08:00
|
|
|
|
2010-07-27 03:15:15 +08:00
|
|
|
This is a good source for looking at the various reporting hooks.
|
2009-07-09 01:18:26 +08:00
|
|
|
"""
|
2017-03-17 09:21:30 +08:00
|
|
|
from __future__ import absolute_import, division, print_function
|
2017-03-05 07:53:42 +08:00
|
|
|
|
2017-03-21 06:59:05 +08:00
|
|
|
import itertools
|
2017-09-28 03:00:55 +08:00
|
|
|
import platform
|
2009-02-27 18:18:27 +08:00
|
|
|
import sys
|
2014-08-01 06:13:40 +08:00
|
|
|
import time
|
2017-09-28 03:00:55 +08:00
|
|
|
|
2017-10-25 05:57:14 +08:00
|
|
|
import pluggy
|
2017-09-28 03:00:55 +08:00
|
|
|
import py
|
|
|
|
import six
|
|
|
|
|
|
|
|
import pytest
|
2017-10-24 17:42:16 +08:00
|
|
|
from _pytest import nodes
|
2017-09-28 03:00:55 +08:00
|
|
|
from _pytest.main import EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, \
|
|
|
|
EXIT_USAGEERROR, EXIT_NOTESTSCOLLECTED
|
2015-08-26 06:43:09 +08:00
|
|
|
|
2009-02-27 18:18:27 +08:00
|
|
|
|
2009-08-03 21:27:26 +08:00
|
|
|
def pytest_addoption(parser):
|
2010-01-03 19:41:29 +08:00
|
|
|
group = parser.getgroup("terminal reporting", "reporting", after="general")
|
2010-07-27 03:15:15 +08:00
|
|
|
group._addoption('-v', '--verbose', action="count",
|
2017-07-17 07:25:07 +08:00
|
|
|
dest="verbose", default=0, help="increase verbosity."),
|
2010-11-01 02:51:16 +08:00
|
|
|
group._addoption('-q', '--quiet', action="count",
|
2017-07-17 07:25:07 +08:00
|
|
|
dest="quiet", default=0, help="decrease verbosity."),
|
2010-05-06 01:50:59 +08:00
|
|
|
group._addoption('-r',
|
2017-07-17 07:25:07 +08:00
|
|
|
action="store", dest="reportchars", default='', metavar="chars",
|
|
|
|
help="show extra test summary info as specified by chars (f)ailed, "
|
|
|
|
"(E)error, (s)skipped, (x)failed, (X)passed, "
|
|
|
|
"(p)passed, (P)passed with output, (a)all except pP. "
|
|
|
|
"Warnings are displayed at all times except when "
|
|
|
|
"--disable-warnings is set")
|
2017-02-25 23:02:48 +08:00
|
|
|
group._addoption('--disable-warnings', '--disable-pytest-warnings', default=False,
|
2017-03-05 03:32:10 +08:00
|
|
|
dest='disable_warnings', action='store_true',
|
2017-03-05 07:53:42 +08:00
|
|
|
help='disable warnings summary')
|
2009-10-17 23:43:59 +08:00
|
|
|
group._addoption('-l', '--showlocals',
|
2017-07-17 07:25:07 +08:00
|
|
|
action="store_true", dest="showlocals", default=False,
|
|
|
|
help="show locals in tracebacks (disabled by default).")
|
2010-07-27 03:15:15 +08:00
|
|
|
group._addoption('--tb', metavar="style",
|
2017-07-17 07:25:07 +08:00
|
|
|
action="store", dest="tbstyle", default='auto',
|
|
|
|
choices=['auto', 'long', 'short', 'no', 'line', 'native'],
|
|
|
|
help="traceback print mode (auto/long/short/line/native/no).")
|
2018-02-01 03:36:28 +08:00
|
|
|
group._addoption('--no-stdout',
|
|
|
|
action="store_false", dest="nostdout",
|
|
|
|
help="Do not print stdout")
|
2013-08-01 22:49:26 +08:00
|
|
|
group._addoption('--fulltrace', '--full-trace',
|
2017-07-17 07:25:07 +08:00
|
|
|
action="store_true", default=False,
|
|
|
|
help="don't cut any tracebacks (default is to cut).")
|
2013-12-07 03:49:48 +08:00
|
|
|
group._addoption('--color', metavar="color",
|
2017-07-17 07:25:07 +08:00
|
|
|
action="store", dest="color", default='auto',
|
|
|
|
choices=['yes', 'no', 'auto'],
|
|
|
|
help="color terminal output (yes/no/auto).")
|
2009-08-03 21:27:26 +08:00
|
|
|
|
2017-09-28 01:42:55 +08:00
|
|
|
parser.addini("console_output_style",
|
2017-11-22 06:43:12 +08:00
|
|
|
help="console output: classic or with additional progress information (classic|progress).",
|
2017-09-28 01:42:55 +08:00
|
|
|
default='progress')
|
|
|
|
|
2017-07-17 07:25:09 +08:00
|
|
|
|
2009-05-19 05:26:16 +08:00
|
|
|
def pytest_configure(config):
|
2010-11-01 02:51:16 +08:00
|
|
|
config.option.verbose -= config.option.quiet
|
2014-04-01 21:03:17 +08:00
|
|
|
reporter = TerminalReporter(config, sys.stdout)
|
2010-09-26 22:23:43 +08:00
|
|
|
config.pluginmanager.register(reporter, 'terminalreporter')
|
2010-11-06 06:37:31 +08:00
|
|
|
if config.option.debug or config.option.traceconfig:
|
|
|
|
def mywriter(tags, args):
|
|
|
|
msg = " ".join(map(str, args))
|
|
|
|
reporter.write_line("[traceconfig] " + msg)
|
|
|
|
config.trace.root.setprocessor("pytest:config", mywriter)
|
2009-02-27 18:18:27 +08:00
|
|
|
|
2017-07-17 07:25:09 +08:00
|
|
|
|
2010-05-06 01:50:59 +08:00
|
|
|
def getreportopt(config):
|
|
|
|
reportopts = ""
|
2010-11-01 06:28:31 +08:00
|
|
|
reportchars = config.option.reportchars
|
2017-03-05 03:32:10 +08:00
|
|
|
if not config.option.disable_warnings and 'w' not in reportchars:
|
2016-06-26 00:16:13 +08:00
|
|
|
reportchars += 'w'
|
2017-03-05 03:32:10 +08:00
|
|
|
elif config.option.disable_warnings and 'w' in reportchars:
|
2016-06-26 00:16:13 +08:00
|
|
|
reportchars = reportchars.replace('w', '')
|
2010-05-06 01:50:59 +08:00
|
|
|
if reportchars:
|
|
|
|
for char in reportchars:
|
2015-09-10 18:13:43 +08:00
|
|
|
if char not in reportopts and char != 'a':
|
2010-05-06 01:50:59 +08:00
|
|
|
reportopts += char
|
2015-09-10 18:13:43 +08:00
|
|
|
elif char == 'a':
|
|
|
|
reportopts = 'fEsxXw'
|
2010-05-06 01:50:59 +08:00
|
|
|
return reportopts
|
2009-10-17 23:42:40 +08:00
|
|
|
|
2017-07-17 07:25:09 +08:00
|
|
|
|
2010-09-26 22:23:44 +08:00
|
|
|
def pytest_report_teststatus(report):
|
|
|
|
if report.passed:
|
|
|
|
letter = "."
|
|
|
|
elif report.skipped:
|
|
|
|
letter = "s"
|
|
|
|
elif report.failed:
|
|
|
|
letter = "F"
|
|
|
|
if report.when != "call":
|
|
|
|
letter = "f"
|
|
|
|
return report.outcome, letter, report.outcome.upper()
|
|
|
|
|
2017-03-17 08:55:03 +08:00
|
|
|
|
2017-05-18 05:16:11 +08:00
|
|
|
class WarningReport:
|
2017-03-17 08:55:03 +08:00
|
|
|
"""
|
|
|
|
Simple structure to hold warnings information captured by ``pytest_logwarning``.
|
|
|
|
"""
|
2017-07-17 07:25:09 +08:00
|
|
|
|
2014-03-12 05:10:17 +08:00
|
|
|
def __init__(self, code, message, nodeid=None, fslocation=None):
|
2017-03-17 08:55:03 +08:00
|
|
|
"""
|
|
|
|
:param code: unused
|
|
|
|
:param str message: user friendly message about the warning
|
|
|
|
:param str|None nodeid: node id that generated the warning (see ``get_location``).
|
|
|
|
:param tuple|py.path.local fslocation:
|
|
|
|
file system location of the source of the warning (see ``get_location``).
|
|
|
|
"""
|
2014-03-12 05:10:17 +08:00
|
|
|
self.code = code
|
|
|
|
self.message = message
|
|
|
|
self.nodeid = nodeid
|
|
|
|
self.fslocation = fslocation
|
|
|
|
|
2017-03-17 08:55:03 +08:00
|
|
|
def get_location(self, config):
|
|
|
|
"""
|
|
|
|
Returns the more user-friendly information about the location
|
|
|
|
of a warning, or None.
|
|
|
|
"""
|
|
|
|
if self.nodeid:
|
|
|
|
return self.nodeid
|
|
|
|
if self.fslocation:
|
2017-04-14 05:01:24 +08:00
|
|
|
if isinstance(self.fslocation, tuple) and len(self.fslocation) >= 2:
|
|
|
|
filename, linenum = self.fslocation[:2]
|
2017-03-17 08:55:03 +08:00
|
|
|
relpath = py.path.local(filename).relto(config.invocation_dir)
|
2017-04-14 05:01:24 +08:00
|
|
|
return '%s:%s' % (relpath, linenum)
|
2017-03-17 08:55:03 +08:00
|
|
|
else:
|
|
|
|
return str(self.fslocation)
|
|
|
|
return None
|
|
|
|
|
2014-03-12 05:10:17 +08:00
|
|
|
|
2017-05-18 05:16:11 +08:00
|
|
|
class TerminalReporter:
|
2009-02-27 18:18:27 +08:00
|
|
|
def __init__(self, config, file=None):
|
2015-07-19 03:39:55 +08:00
|
|
|
import _pytest.config
|
2010-07-27 03:15:15 +08:00
|
|
|
self.config = config
|
2010-11-01 02:51:16 +08:00
|
|
|
self.verbosity = self.config.option.verbose
|
|
|
|
self.showheader = self.verbosity >= 0
|
|
|
|
self.showfspath = self.verbosity >= 0
|
|
|
|
self.showlongtestinfo = self.verbosity > 0
|
2010-11-22 18:59:56 +08:00
|
|
|
self._numcollected = 0
|
2017-09-28 01:42:55 +08:00
|
|
|
self._session = None
|
2010-11-01 02:51:16 +08:00
|
|
|
|
2010-07-27 03:15:15 +08:00
|
|
|
self.stats = {}
|
2015-02-27 04:56:44 +08:00
|
|
|
self.startdir = py.path.local()
|
2009-02-27 18:18:27 +08:00
|
|
|
if file is None:
|
2014-08-01 06:13:40 +08:00
|
|
|
file = sys.stdout
|
2017-11-24 05:26:57 +08:00
|
|
|
self._tw = _pytest.config.create_terminal_writer(config, file)
|
2017-12-01 04:34:53 +08:00
|
|
|
# self.writer will be deprecated in pytest-3.4
|
|
|
|
self.writer = self._tw
|
2017-11-24 05:26:57 +08:00
|
|
|
self._screen_width = self._tw.fullwidth
|
2010-07-27 03:15:15 +08:00
|
|
|
self.currentfspath = None
|
2010-05-06 01:50:59 +08:00
|
|
|
self.reportchars = getreportopt(config)
|
2017-11-24 05:26:57 +08:00
|
|
|
self.hasmarkup = self._tw.hasmarkup
|
2016-02-21 00:21:42 +08:00
|
|
|
self.isatty = file.isatty()
|
2018-01-12 06:22:18 +08:00
|
|
|
self._progress_nodeids_reported = set()
|
2018-01-12 17:04:43 +08:00
|
|
|
self._show_progress_info = self._determine_show_progress_info()
|
|
|
|
|
|
|
|
def _determine_show_progress_info(self):
|
|
|
|
"""Return True if we should display progress information based on the current config"""
|
|
|
|
# do not show progress if we are not capturing output (#3038)
|
|
|
|
if self.config.getoption('capture') == 'no':
|
|
|
|
return False
|
|
|
|
# do not show progress if we are showing fixture setup/teardown
|
|
|
|
if self.config.getoption('setupshow'):
|
|
|
|
return False
|
|
|
|
return self.config.getini('console_output_style') == 'progress'
|
2009-10-17 23:42:40 +08:00
|
|
|
|
2010-05-06 01:50:59 +08:00
|
|
|
def hasopt(self, char):
|
2013-07-06 21:43:59 +08:00
|
|
|
char = {'xfailed': 'x', 'skipped': 's'}.get(char, char)
|
2010-05-06 01:50:59 +08:00
|
|
|
return char in self.reportchars
|
2009-02-27 18:18:27 +08:00
|
|
|
|
2015-02-27 04:56:44 +08:00
|
|
|
def write_fspath_result(self, nodeid, res):
|
|
|
|
fspath = self.config.rootdir.join(nodeid.split("::")[0])
|
2009-02-27 18:18:27 +08:00
|
|
|
if fspath != self.currentfspath:
|
2017-09-28 01:42:55 +08:00
|
|
|
if self.currentfspath is not None:
|
|
|
|
self._write_progress_information_filling_space()
|
2010-09-26 22:23:45 +08:00
|
|
|
self.currentfspath = fspath
|
2015-02-27 04:56:44 +08:00
|
|
|
fspath = self.startdir.bestrelpath(fspath)
|
2017-11-24 05:26:57 +08:00
|
|
|
self._tw.line()
|
|
|
|
self._tw.write(fspath + " ")
|
|
|
|
self._tw.write(res)
|
2009-02-27 18:18:27 +08:00
|
|
|
|
2009-03-11 09:40:08 +08:00
|
|
|
def write_ensure_prefix(self, prefix, extra="", **kwargs):
|
2009-02-27 18:18:27 +08:00
|
|
|
if self.currentfspath != prefix:
|
2017-11-24 05:26:57 +08:00
|
|
|
self._tw.line()
|
2010-07-27 03:15:15 +08:00
|
|
|
self.currentfspath = prefix
|
2017-11-24 05:26:57 +08:00
|
|
|
self._tw.write(prefix)
|
2009-02-27 18:18:27 +08:00
|
|
|
if extra:
|
2017-11-24 05:26:57 +08:00
|
|
|
self._tw.write(extra, **kwargs)
|
2009-02-27 18:18:27 +08:00
|
|
|
self.currentfspath = -2
|
|
|
|
|
|
|
|
def ensure_newline(self):
|
2010-07-27 03:15:15 +08:00
|
|
|
if self.currentfspath:
|
2017-11-24 05:26:57 +08:00
|
|
|
self._tw.line()
|
2009-02-27 18:18:27 +08:00
|
|
|
self.currentfspath = None
|
|
|
|
|
2010-11-26 20:37:00 +08:00
|
|
|
def write(self, content, **markup):
|
2017-11-24 05:26:57 +08:00
|
|
|
self._tw.write(content, **markup)
|
2010-11-26 20:37:00 +08:00
|
|
|
|
2009-02-27 18:18:27 +08:00
|
|
|
def write_line(self, line, **markup):
|
2017-08-03 01:33:52 +08:00
|
|
|
if not isinstance(line, six.text_type):
|
|
|
|
line = six.text_type(line, errors="replace")
|
2009-02-27 18:18:27 +08:00
|
|
|
self.ensure_newline()
|
2017-11-24 05:26:57 +08:00
|
|
|
self._tw.line(line, **markup)
|
2009-02-27 18:18:27 +08:00
|
|
|
|
2010-11-22 18:59:56 +08:00
|
|
|
def rewrite(self, line, **markup):
|
2017-08-04 07:54:33 +08:00
|
|
|
"""
|
|
|
|
Rewinds the terminal cursor to the beginning and writes the given line.
|
|
|
|
|
|
|
|
:kwarg erase: if True, will also add spaces until the full terminal width to ensure
|
|
|
|
previous lines are properly erased.
|
|
|
|
|
|
|
|
The rest of the keyword arguments are markup instructions.
|
|
|
|
"""
|
|
|
|
erase = markup.pop('erase', False)
|
|
|
|
if erase:
|
2017-11-24 05:26:57 +08:00
|
|
|
fill_count = self._tw.fullwidth - len(line) - 1
|
2017-08-04 07:54:33 +08:00
|
|
|
fill = ' ' * fill_count
|
|
|
|
else:
|
|
|
|
fill = ''
|
2010-11-22 18:59:56 +08:00
|
|
|
line = str(line)
|
2017-11-24 05:26:57 +08:00
|
|
|
self._tw.write("\r" + line + fill, **markup)
|
2010-11-22 18:59:56 +08:00
|
|
|
|
2009-02-27 18:18:27 +08:00
|
|
|
def write_sep(self, sep, title=None, **markup):
|
|
|
|
self.ensure_newline()
|
2017-11-24 05:26:57 +08:00
|
|
|
self._tw.sep(sep, title, **markup)
|
2009-02-27 18:18:27 +08:00
|
|
|
|
2013-09-27 21:48:03 +08:00
|
|
|
def section(self, title, sep="=", **kw):
|
2017-11-24 05:26:57 +08:00
|
|
|
self._tw.sep(sep, title, **kw)
|
2013-09-27 21:48:03 +08:00
|
|
|
|
|
|
|
def line(self, msg, **kw):
|
2017-11-24 05:26:57 +08:00
|
|
|
self._tw.line(msg, **kw)
|
2013-09-27 21:48:03 +08:00
|
|
|
|
2009-04-09 08:12:10 +08:00
|
|
|
def pytest_internalerror(self, excrepr):
|
2017-08-03 01:33:52 +08:00
|
|
|
for line in six.text_type(excrepr).split("\n"):
|
2009-04-03 22:18:47 +08:00
|
|
|
self.write_line("INTERNALERROR> " + line)
|
2010-10-05 23:21:41 +08:00
|
|
|
return 1
|
2009-02-27 18:18:27 +08:00
|
|
|
|
2014-03-12 05:10:17 +08:00
|
|
|
def pytest_logwarning(self, code, fslocation, message, nodeid):
|
|
|
|
warnings = self.stats.setdefault("warnings", [])
|
|
|
|
warning = WarningReport(code=code, fslocation=fslocation,
|
|
|
|
message=message, nodeid=nodeid)
|
|
|
|
warnings.append(warning)
|
|
|
|
|
2010-04-22 17:57:57 +08:00
|
|
|
def pytest_plugin_registered(self, plugin):
|
2010-07-27 03:15:15 +08:00
|
|
|
if self.config.option.traceconfig:
|
2013-07-06 21:43:59 +08:00
|
|
|
msg = "PLUGIN registered: %s" % (plugin,)
|
2010-07-27 03:15:15 +08:00
|
|
|
# XXX this event may happen during setup/teardown time
|
|
|
|
# which unfortunately captures our output here
|
|
|
|
# which garbles our output if we use self.write_line
|
2010-04-22 17:57:57 +08:00
|
|
|
self.write_line(msg)
|
|
|
|
|
2009-04-09 07:50:02 +08:00
|
|
|
def pytest_deselected(self, items):
|
2010-09-15 16:30:50 +08:00
|
|
|
self.stats.setdefault('deselected', []).extend(items)
|
2009-05-09 00:47:33 +08:00
|
|
|
|
2010-11-06 16:58:04 +08:00
|
|
|
def pytest_runtest_logstart(self, nodeid, location):
|
2010-09-26 22:23:45 +08:00
|
|
|
# ensure that the path is printed before the
|
|
|
|
# 1st test of a module starts running
|
2010-11-01 02:51:16 +08:00
|
|
|
if self.showlongtestinfo:
|
2015-02-27 04:56:44 +08:00
|
|
|
line = self._locationline(nodeid, *location)
|
2010-09-26 22:23:45 +08:00
|
|
|
self.write_ensure_prefix(line, "")
|
2010-11-01 02:51:16 +08:00
|
|
|
elif self.showfspath:
|
2015-02-27 04:56:44 +08:00
|
|
|
fsid = nodeid.split("::")[0]
|
|
|
|
self.write_fspath_result(fsid, "")
|
2010-09-26 22:23:45 +08:00
|
|
|
|
2009-08-04 18:00:04 +08:00
|
|
|
def pytest_runtest_logreport(self, report):
|
|
|
|
rep = report
|
2010-09-26 22:23:44 +08:00
|
|
|
res = self.config.hook.pytest_report_teststatus(report=rep)
|
|
|
|
cat, letter, word = res
|
2017-09-28 01:42:55 +08:00
|
|
|
if isinstance(word, tuple):
|
|
|
|
word, markup = word
|
|
|
|
else:
|
|
|
|
markup = None
|
2010-09-26 22:23:44 +08:00
|
|
|
self.stats.setdefault(cat, []).append(rep)
|
2013-09-27 21:48:03 +08:00
|
|
|
self._tests_ran = True
|
2009-07-31 20:22:02 +08:00
|
|
|
if not letter and not word:
|
|
|
|
# probably passed setup/teardown
|
|
|
|
return
|
2017-09-28 01:42:55 +08:00
|
|
|
running_xdist = hasattr(rep, 'node')
|
2010-11-01 02:51:16 +08:00
|
|
|
if self.verbosity <= 0:
|
2017-09-28 01:42:55 +08:00
|
|
|
if not running_xdist and self.showfspath:
|
2015-02-27 04:56:44 +08:00
|
|
|
self.write_fspath_result(rep.nodeid, letter)
|
2010-09-28 21:53:10 +08:00
|
|
|
else:
|
2017-11-24 05:26:57 +08:00
|
|
|
self._tw.write(letter)
|
2009-02-27 18:18:27 +08:00
|
|
|
else:
|
2018-01-12 06:22:18 +08:00
|
|
|
self._progress_nodeids_reported.add(rep.nodeid)
|
2017-09-28 01:42:55 +08:00
|
|
|
if markup is None:
|
2010-09-28 21:53:10 +08:00
|
|
|
if rep.passed:
|
2017-07-17 07:25:08 +08:00
|
|
|
markup = {'green': True}
|
2010-09-28 21:53:10 +08:00
|
|
|
elif rep.failed:
|
2017-07-17 07:25:08 +08:00
|
|
|
markup = {'red': True}
|
2010-09-28 21:53:10 +08:00
|
|
|
elif rep.skipped:
|
2017-07-17 07:25:08 +08:00
|
|
|
markup = {'yellow': True}
|
2017-09-28 03:00:55 +08:00
|
|
|
else:
|
|
|
|
markup = {}
|
2015-02-27 04:56:44 +08:00
|
|
|
line = self._locationline(rep.nodeid, *rep.location)
|
2017-09-28 01:42:55 +08:00
|
|
|
if not running_xdist:
|
2009-03-25 19:50:57 +08:00
|
|
|
self.write_ensure_prefix(line, word, **markup)
|
2018-01-12 06:22:18 +08:00
|
|
|
if self._show_progress_info:
|
|
|
|
self._write_progress_information_filling_space()
|
2009-03-25 19:50:57 +08:00
|
|
|
else:
|
|
|
|
self.ensure_newline()
|
2017-11-24 05:26:57 +08:00
|
|
|
self._tw.write("[%s]" % rep.node.gateway.id)
|
2017-11-22 06:43:12 +08:00
|
|
|
if self._show_progress_info:
|
2017-11-24 05:26:57 +08:00
|
|
|
self._tw.write(self._get_progress_information_message() + " ", cyan=True)
|
2017-11-22 06:43:12 +08:00
|
|
|
else:
|
2017-11-24 05:26:57 +08:00
|
|
|
self._tw.write(' ')
|
|
|
|
self._tw.write(word, **markup)
|
|
|
|
self._tw.write(" " + line)
|
2009-03-25 19:50:57 +08:00
|
|
|
self.currentfspath = -2
|
2009-02-27 18:18:27 +08:00
|
|
|
|
2018-01-12 06:22:18 +08:00
|
|
|
def pytest_runtest_logfinish(self, nodeid):
|
|
|
|
if self.verbosity <= 0 and self._show_progress_info:
|
|
|
|
self._progress_nodeids_reported.add(nodeid)
|
|
|
|
last_item = len(self._progress_nodeids_reported) == self._session.testscollected
|
|
|
|
if last_item:
|
|
|
|
self._write_progress_information_filling_space()
|
|
|
|
else:
|
|
|
|
past_edge = self._tw.chars_on_current_line + self._PROGRESS_LENGTH + 1 >= self._screen_width
|
|
|
|
if past_edge:
|
|
|
|
msg = self._get_progress_information_message()
|
|
|
|
self._tw.write(msg + '\n', cyan=True)
|
2017-09-28 01:42:55 +08:00
|
|
|
|
|
|
|
_PROGRESS_LENGTH = len(' [100%]')
|
|
|
|
|
|
|
|
def _get_progress_information_message(self):
|
2017-11-29 07:54:14 +08:00
|
|
|
collected = self._session.testscollected
|
|
|
|
if collected:
|
2018-01-12 06:22:18 +08:00
|
|
|
progress = len(self._progress_nodeids_reported) * 100 // collected
|
2017-11-29 07:54:14 +08:00
|
|
|
return ' [{:3d}%]'.format(progress)
|
|
|
|
return ' [100%]'
|
2017-09-28 01:42:55 +08:00
|
|
|
|
|
|
|
def _write_progress_information_filling_space(self):
|
|
|
|
msg = self._get_progress_information_message()
|
2017-11-24 05:26:57 +08:00
|
|
|
fill = ' ' * (self._tw.fullwidth - self._tw.chars_on_current_line - len(msg) - 1)
|
2017-09-28 01:42:55 +08:00
|
|
|
self.write(fill + msg, cyan=True)
|
|
|
|
|
2010-11-22 18:59:56 +08:00
|
|
|
def pytest_collection(self):
|
2016-02-21 00:21:42 +08:00
|
|
|
if not self.isatty and self.config.option.verbose >= 1:
|
2010-11-26 20:37:00 +08:00
|
|
|
self.write("collecting ... ", bold=True)
|
2010-11-22 18:59:56 +08:00
|
|
|
|
2009-08-04 18:00:04 +08:00
|
|
|
def pytest_collectreport(self, report):
|
2010-11-22 18:59:56 +08:00
|
|
|
if report.failed:
|
|
|
|
self.stats.setdefault("error", []).append(report)
|
|
|
|
elif report.skipped:
|
|
|
|
self.stats.setdefault("skipped", []).append(report)
|
|
|
|
items = [x for x in report.result if isinstance(x, pytest.Item)]
|
|
|
|
self._numcollected += len(items)
|
2016-02-21 00:21:42 +08:00
|
|
|
if self.isatty:
|
2017-07-17 07:25:09 +08:00
|
|
|
# self.write_fspath_result(report.nodeid, 'E')
|
2010-11-22 18:59:56 +08:00
|
|
|
self.report_collect()
|
|
|
|
|
|
|
|
def report_collect(self, final=False):
|
2012-10-07 19:06:17 +08:00
|
|
|
if self.config.option.verbose < 0:
|
|
|
|
return
|
|
|
|
|
2010-11-22 18:59:56 +08:00
|
|
|
errors = len(self.stats.get('error', []))
|
|
|
|
skipped = len(self.stats.get('skipped', []))
|
|
|
|
if final:
|
|
|
|
line = "collected "
|
|
|
|
else:
|
|
|
|
line = "collecting "
|
2017-06-04 05:42:26 +08:00
|
|
|
line += str(self._numcollected) + " item" + ('' if self._numcollected == 1 else 's')
|
2010-11-22 18:59:56 +08:00
|
|
|
if errors:
|
|
|
|
line += " / %d errors" % errors
|
|
|
|
if skipped:
|
|
|
|
line += " / %d skipped" % skipped
|
2016-02-21 00:21:42 +08:00
|
|
|
if self.isatty:
|
2017-08-04 07:54:33 +08:00
|
|
|
self.rewrite(line, bold=True, erase=True)
|
2010-11-22 18:59:56 +08:00
|
|
|
if final:
|
2017-08-04 07:54:33 +08:00
|
|
|
self.write('\n')
|
2010-11-22 18:59:56 +08:00
|
|
|
else:
|
|
|
|
self.write_line(line)
|
|
|
|
|
|
|
|
def pytest_collection_modifyitems(self):
|
|
|
|
self.report_collect(True)
|
2009-02-27 18:18:27 +08:00
|
|
|
|
2015-05-06 16:08:08 +08:00
|
|
|
@pytest.hookimpl(trylast=True)
|
2009-05-23 03:53:26 +08:00
|
|
|
def pytest_sessionstart(self, session):
|
2017-09-28 01:42:55 +08:00
|
|
|
self._session = session
|
2014-08-01 06:13:40 +08:00
|
|
|
self._sessionstarttime = time.time()
|
2010-11-01 02:51:16 +08:00
|
|
|
if not self.showheader:
|
|
|
|
return
|
|
|
|
self.write_sep("=", "test session starts", bold=True)
|
2015-06-01 03:31:31 +08:00
|
|
|
verinfo = platform.python_version()
|
2010-05-22 19:59:01 +08:00
|
|
|
msg = "platform %s -- Python %s" % (sys.platform, verinfo)
|
2010-11-26 03:06:42 +08:00
|
|
|
if hasattr(sys, 'pypy_version_info'):
|
|
|
|
verinfo = ".".join(map(str, sys.pypy_version_info[:3]))
|
2011-07-06 16:18:11 +08:00
|
|
|
msg += "[pypy-%s-%s]" % (verinfo, sys.pypy_version_info[3])
|
2015-05-06 03:53:04 +08:00
|
|
|
msg += ", pytest-%s, py-%s, pluggy-%s" % (
|
|
|
|
pytest.__version__, py.__version__, pluggy.__version__)
|
2010-11-01 02:51:16 +08:00
|
|
|
if self.verbosity > 0 or self.config.option.debug or \
|
2010-09-26 22:23:43 +08:00
|
|
|
getattr(self.config.option, 'pastebin', None):
|
2009-08-19 01:04:57 +08:00
|
|
|
msg += " -- " + str(sys.executable)
|
2009-03-21 09:31:27 +08:00
|
|
|
self.write_line(msg)
|
2012-06-21 17:07:22 +08:00
|
|
|
lines = self.config.hook.pytest_report_header(
|
|
|
|
config=self.config, startdir=self.startdir)
|
2017-07-27 21:34:49 +08:00
|
|
|
self._write_report_lines_from_hooks(lines)
|
|
|
|
|
|
|
|
def _write_report_lines_from_hooks(self, lines):
|
2010-01-13 04:43:25 +08:00
|
|
|
lines.reverse()
|
|
|
|
for line in flatten(lines):
|
|
|
|
self.write_line(line)
|
2010-09-26 22:23:43 +08:00
|
|
|
|
2012-06-20 06:16:47 +08:00
|
|
|
def pytest_report_header(self, config):
|
2015-02-27 04:56:44 +08:00
|
|
|
inifile = ""
|
|
|
|
if config.inifile:
|
2017-03-06 03:44:13 +08:00
|
|
|
inifile = " " + config.rootdir.bestrelpath(config.inifile)
|
|
|
|
lines = ["rootdir: %s, inifile:%s" % (config.rootdir, inifile)]
|
2015-05-06 03:53:04 +08:00
|
|
|
|
|
|
|
plugininfo = config.pluginmanager.list_plugin_distinfo()
|
2012-06-20 06:16:47 +08:00
|
|
|
if plugininfo:
|
2015-08-17 15:10:01 +08:00
|
|
|
|
|
|
|
lines.append(
|
|
|
|
"plugins: %s" % ", ".join(_plugin_nameversions(plugininfo)))
|
2015-02-27 04:56:44 +08:00
|
|
|
return lines
|
2012-06-20 06:16:47 +08:00
|
|
|
|
2011-03-07 01:32:00 +08:00
|
|
|
def pytest_collection_finish(self, session):
|
|
|
|
if self.config.option.collectonly:
|
|
|
|
self._printcollecteditems(session.items)
|
|
|
|
if self.stats.get('failed'):
|
2017-11-24 05:26:57 +08:00
|
|
|
self._tw.sep("!", "collection failures")
|
2011-03-07 01:32:00 +08:00
|
|
|
for rep in self.stats.get('failed'):
|
2017-11-24 05:26:57 +08:00
|
|
|
rep.toterminal(self._tw)
|
2011-03-07 01:32:00 +08:00
|
|
|
return 1
|
|
|
|
return 0
|
2017-07-27 21:34:49 +08:00
|
|
|
lines = self.config.hook.pytest_report_collectionfinish(
|
|
|
|
config=self.config, startdir=self.startdir, items=session.items)
|
|
|
|
self._write_report_lines_from_hooks(lines)
|
2011-03-08 20:37:00 +08:00
|
|
|
|
2011-03-07 01:32:00 +08:00
|
|
|
def _printcollecteditems(self, items):
|
|
|
|
# to print out items and their parent collectors
|
|
|
|
# we take care to leave out Instances aka ()
|
|
|
|
# because later versions are going to get rid of them anyway
|
|
|
|
if self.config.option.verbose < 0:
|
2012-02-03 23:56:06 +08:00
|
|
|
if self.config.option.verbose < -1:
|
|
|
|
counts = {}
|
|
|
|
for item in items:
|
|
|
|
name = item.nodeid.split('::', 1)[0]
|
|
|
|
counts[name] = counts.get(name, 0) + 1
|
|
|
|
for name, count in sorted(counts.items()):
|
2017-11-24 05:26:57 +08:00
|
|
|
self._tw.line("%s: %d" % (name, count))
|
2012-02-03 23:56:06 +08:00
|
|
|
else:
|
|
|
|
for item in items:
|
|
|
|
nodeid = item.nodeid
|
|
|
|
nodeid = nodeid.replace("::()::", "::")
|
2017-11-24 05:26:57 +08:00
|
|
|
self._tw.line(nodeid)
|
2011-03-07 01:32:00 +08:00
|
|
|
return
|
|
|
|
stack = []
|
|
|
|
indent = ""
|
|
|
|
for item in items:
|
2017-07-17 07:25:09 +08:00
|
|
|
needed_collectors = item.listchain()[1:] # strip root node
|
2011-03-07 01:32:00 +08:00
|
|
|
while stack:
|
|
|
|
if stack == needed_collectors[:len(stack)]:
|
|
|
|
break
|
|
|
|
stack.pop()
|
|
|
|
for col in needed_collectors[len(stack):]:
|
|
|
|
stack.append(col)
|
2017-07-17 07:25:09 +08:00
|
|
|
# if col.name == "()":
|
2011-03-07 01:32:00 +08:00
|
|
|
# continue
|
2013-07-06 21:43:59 +08:00
|
|
|
indent = (len(stack) - 1) * " "
|
2017-11-24 05:26:57 +08:00
|
|
|
self._tw.line("%s%s" % (indent, col))
|
2009-02-27 18:18:27 +08:00
|
|
|
|
2015-05-06 16:08:08 +08:00
|
|
|
@pytest.hookimpl(hookwrapper=True)
|
2014-10-09 02:23:40 +08:00
|
|
|
def pytest_sessionfinish(self, exitstatus):
|
|
|
|
outcome = yield
|
|
|
|
outcome.get_result()
|
2017-11-24 05:26:57 +08:00
|
|
|
self._tw.line("")
|
2015-07-05 01:42:22 +08:00
|
|
|
summary_exit_codes = (
|
|
|
|
EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, EXIT_USAGEERROR,
|
|
|
|
EXIT_NOTESTSCOLLECTED)
|
|
|
|
if exitstatus in summary_exit_codes:
|
2016-08-14 21:02:35 +08:00
|
|
|
self.config.hook.pytest_terminal_summary(terminalreporter=self,
|
|
|
|
exitstatus=exitstatus)
|
2009-07-31 20:22:02 +08:00
|
|
|
self.summary_errors()
|
2009-02-27 18:18:27 +08:00
|
|
|
self.summary_failures()
|
2014-03-12 05:10:17 +08:00
|
|
|
self.summary_warnings()
|
2015-09-19 14:36:43 +08:00
|
|
|
self.summary_passes()
|
2015-07-05 01:42:22 +08:00
|
|
|
if exitstatus == EXIT_INTERRUPTED:
|
2009-07-20 20:01:40 +08:00
|
|
|
self._report_keyboardinterrupt()
|
2011-07-08 03:24:09 +08:00
|
|
|
del self._keyboardinterrupt_memo
|
2009-02-27 18:18:27 +08:00
|
|
|
self.summary_deselected()
|
|
|
|
self.summary_stats()
|
|
|
|
|
2009-07-20 20:01:40 +08:00
|
|
|
def pytest_keyboard_interrupt(self, excinfo):
|
2009-12-25 16:53:36 +08:00
|
|
|
self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True)
|
2009-07-20 20:01:40 +08:00
|
|
|
|
2011-07-08 03:24:09 +08:00
|
|
|
def pytest_unconfigure(self):
|
|
|
|
if hasattr(self, '_keyboardinterrupt_memo'):
|
|
|
|
self._report_keyboardinterrupt()
|
|
|
|
|
2009-07-20 20:01:40 +08:00
|
|
|
def _report_keyboardinterrupt(self):
|
|
|
|
excrepr = self._keyboardinterrupt_memo
|
2010-05-25 22:52:09 +08:00
|
|
|
msg = excrepr.reprcrash.message
|
|
|
|
self.write_sep("!", msg)
|
|
|
|
if "KeyboardInterrupt" in msg:
|
2010-11-01 06:28:31 +08:00
|
|
|
if self.config.option.fulltrace:
|
2017-11-24 05:26:57 +08:00
|
|
|
excrepr.toterminal(self._tw)
|
2010-05-25 22:52:09 +08:00
|
|
|
else:
|
2017-11-24 05:26:57 +08:00
|
|
|
self._tw.line("to show a full traceback on KeyboardInterrupt use --fulltrace", yellow=True)
|
|
|
|
excrepr.reprcrash.toterminal(self._tw)
|
2009-07-20 20:01:40 +08:00
|
|
|
|
2015-02-27 04:56:44 +08:00
|
|
|
def _locationline(self, nodeid, fspath, lineno, domain):
|
|
|
|
def mkrel(nodeid):
|
|
|
|
line = self.config.cwd_relative_nodeid(nodeid)
|
|
|
|
if domain and line.endswith(domain):
|
|
|
|
line = line[:-len(domain)]
|
2017-11-04 23:17:20 +08:00
|
|
|
values = domain.split("[")
|
|
|
|
values[0] = values[0].replace('.', '::') # don't replace '.' in params
|
|
|
|
line += "[".join(values)
|
2015-02-27 04:56:44 +08:00
|
|
|
return line
|
2011-03-08 20:44:53 +08:00
|
|
|
# collect_fspath comes from testid which has a "/"-normalized path
|
2015-02-27 04:56:44 +08:00
|
|
|
|
2011-03-08 20:44:53 +08:00
|
|
|
if fspath:
|
2015-02-27 04:56:44 +08:00
|
|
|
res = mkrel(nodeid).replace("::()", "") # parens-normalization
|
2017-10-24 17:42:16 +08:00
|
|
|
if nodeid.split("::")[0] != fspath.replace("\\", nodes.SEP):
|
2015-02-27 04:56:44 +08:00
|
|
|
res += " <- " + self.startdir.bestrelpath(fspath)
|
2009-05-06 05:52:25 +08:00
|
|
|
else:
|
2015-02-27 04:56:44 +08:00
|
|
|
res = "[location]"
|
|
|
|
return res + " "
|
2010-07-27 03:15:15 +08:00
|
|
|
|
2009-05-12 17:05:05 +08:00
|
|
|
def _getfailureheadline(self, rep):
|
2010-09-26 22:23:44 +08:00
|
|
|
if hasattr(rep, 'location'):
|
|
|
|
fspath, lineno, domain = rep.location
|
|
|
|
return domain
|
2009-07-31 20:22:02 +08:00
|
|
|
else:
|
2017-07-17 07:25:09 +08:00
|
|
|
return "test session" # XXX?
|
2009-05-12 23:02:22 +08:00
|
|
|
|
2010-09-26 22:23:44 +08:00
|
|
|
def _getcrashline(self, rep):
|
2009-08-17 22:45:52 +08:00
|
|
|
try:
|
2010-09-26 22:23:44 +08:00
|
|
|
return str(rep.longrepr.reprcrash)
|
2009-08-17 22:45:52 +08:00
|
|
|
except AttributeError:
|
2010-09-26 22:23:44 +08:00
|
|
|
try:
|
|
|
|
return str(rep.longrepr)[:50]
|
|
|
|
except AttributeError:
|
|
|
|
return ""
|
2009-08-17 22:45:52 +08:00
|
|
|
|
2009-02-27 18:18:27 +08:00
|
|
|
#
|
2010-07-27 03:15:15 +08:00
|
|
|
# summaries for sessionfinish
|
2009-02-27 18:18:27 +08:00
|
|
|
#
|
2010-11-23 23:10:47 +08:00
|
|
|
def getreports(self, name):
|
2017-11-04 23:17:20 +08:00
|
|
|
values = []
|
2010-11-23 23:10:47 +08:00
|
|
|
for x in self.stats.get(name, []):
|
|
|
|
if not hasattr(x, '_pdbshown'):
|
2017-11-04 23:17:20 +08:00
|
|
|
values.append(x)
|
|
|
|
return values
|
2009-02-27 18:18:27 +08:00
|
|
|
|
2014-03-12 05:10:17 +08:00
|
|
|
def summary_warnings(self):
|
|
|
|
if self.hasopt("w"):
|
2017-03-05 07:53:42 +08:00
|
|
|
all_warnings = self.stats.get("warnings")
|
|
|
|
if not all_warnings:
|
2014-03-12 05:10:17 +08:00
|
|
|
return
|
2017-03-17 08:55:03 +08:00
|
|
|
|
|
|
|
grouped = itertools.groupby(all_warnings, key=lambda wr: wr.get_location(self.config))
|
|
|
|
|
2017-03-05 07:53:42 +08:00
|
|
|
self.write_sep("=", "warnings summary", yellow=True, bold=False)
|
2017-09-28 03:00:55 +08:00
|
|
|
for location, warning_records in grouped:
|
2017-11-24 05:26:57 +08:00
|
|
|
self._tw.line(str(location) or '<undetermined location>')
|
2017-09-28 03:00:55 +08:00
|
|
|
for w in warning_records:
|
2017-03-17 08:55:03 +08:00
|
|
|
lines = w.message.splitlines()
|
|
|
|
indented = '\n'.join(' ' + x for x in lines)
|
2017-11-24 05:26:57 +08:00
|
|
|
self._tw.line(indented)
|
|
|
|
self._tw.line()
|
|
|
|
self._tw.line('-- Docs: http://doc.pytest.org/en/latest/warnings.html')
|
2014-03-12 05:10:17 +08:00
|
|
|
|
2015-09-19 14:36:43 +08:00
|
|
|
def summary_passes(self):
|
|
|
|
if self.config.option.tbstyle != "no":
|
|
|
|
if self.hasopt("P"):
|
|
|
|
reports = self.getreports('passed')
|
|
|
|
if not reports:
|
|
|
|
return
|
|
|
|
self.write_sep("=", "PASSES")
|
|
|
|
for rep in reports:
|
|
|
|
msg = self._getfailureheadline(rep)
|
|
|
|
self.write_sep("_", msg)
|
|
|
|
self._outrep_summary(rep)
|
|
|
|
|
2016-10-30 16:46:08 +08:00
|
|
|
def print_teardown_sections(self, rep):
|
|
|
|
for secname, content in rep.sections:
|
|
|
|
if 'teardown' in secname:
|
2017-11-24 05:26:57 +08:00
|
|
|
self._tw.sep('-', secname)
|
2016-10-30 16:46:08 +08:00
|
|
|
if content[-1:] == "\n":
|
|
|
|
content = content[:-1]
|
2017-11-24 05:26:57 +08:00
|
|
|
self._tw.line(content)
|
2016-10-30 16:46:08 +08:00
|
|
|
|
2009-02-27 18:18:27 +08:00
|
|
|
def summary_failures(self):
|
2010-11-23 23:10:47 +08:00
|
|
|
if self.config.option.tbstyle != "no":
|
|
|
|
reports = self.getreports('failed')
|
|
|
|
if not reports:
|
|
|
|
return
|
2009-02-27 18:18:27 +08:00
|
|
|
self.write_sep("=", "FAILURES")
|
2010-11-23 23:10:47 +08:00
|
|
|
for rep in reports:
|
|
|
|
if self.config.option.tbstyle == "line":
|
2010-09-26 22:23:44 +08:00
|
|
|
line = self._getcrashline(rep)
|
2010-01-27 19:52:19 +08:00
|
|
|
self.write_line(line)
|
2010-07-27 03:15:15 +08:00
|
|
|
else:
|
2010-01-27 19:52:19 +08:00
|
|
|
msg = self._getfailureheadline(rep)
|
2015-10-28 16:12:57 +08:00
|
|
|
markup = {'red': True, 'bold': True}
|
|
|
|
self.write_sep("_", msg, **markup)
|
2011-07-13 05:09:03 +08:00
|
|
|
self._outrep_summary(rep)
|
2016-10-30 16:46:08 +08:00
|
|
|
for report in self.getreports(''):
|
|
|
|
if report.nodeid == rep.nodeid and report.when == 'teardown':
|
|
|
|
self.print_teardown_sections(report)
|
2009-07-31 20:22:02 +08:00
|
|
|
|
|
|
|
def summary_errors(self):
|
2010-11-23 23:10:47 +08:00
|
|
|
if self.config.option.tbstyle != "no":
|
|
|
|
reports = self.getreports('error')
|
|
|
|
if not reports:
|
|
|
|
return
|
2009-07-31 20:22:02 +08:00
|
|
|
self.write_sep("=", "ERRORS")
|
|
|
|
for rep in self.stats['error']:
|
|
|
|
msg = self._getfailureheadline(rep)
|
|
|
|
if not hasattr(rep, 'when'):
|
|
|
|
# collect
|
2010-07-04 23:06:50 +08:00
|
|
|
msg = "ERROR collecting " + msg
|
2009-07-31 20:22:02 +08:00
|
|
|
elif rep.when == "setup":
|
2010-07-27 03:15:15 +08:00
|
|
|
msg = "ERROR at setup of " + msg
|
2009-07-31 20:22:02 +08:00
|
|
|
elif rep.when == "teardown":
|
2010-07-27 03:15:15 +08:00
|
|
|
msg = "ERROR at teardown of " + msg
|
2009-07-31 20:22:02 +08:00
|
|
|
self.write_sep("_", msg)
|
2011-07-13 05:09:03 +08:00
|
|
|
self._outrep_summary(rep)
|
|
|
|
|
|
|
|
def _outrep_summary(self, rep):
|
2017-11-24 05:26:57 +08:00
|
|
|
rep.toterminal(self._tw)
|
2011-07-13 05:09:03 +08:00
|
|
|
for secname, content in rep.sections:
|
2018-02-01 03:36:28 +08:00
|
|
|
if not self.config.option.nostdout and 'stdout' in secname:
|
|
|
|
continue
|
2017-11-24 05:26:57 +08:00
|
|
|
self._tw.sep("-", secname)
|
2011-07-13 05:09:03 +08:00
|
|
|
if content[-1:] == "\n":
|
|
|
|
content = content[:-1]
|
2017-11-24 05:26:57 +08:00
|
|
|
self._tw.line(content)
|
2009-02-27 18:18:27 +08:00
|
|
|
|
|
|
|
def summary_stats(self):
|
2014-08-01 06:13:40 +08:00
|
|
|
session_duration = time.time() - self._sessionstarttime
|
2015-07-03 01:03:05 +08:00
|
|
|
(line, color) = build_summary_stats_line(self.stats)
|
2013-07-06 21:43:59 +08:00
|
|
|
msg = "%s in %.2f seconds" % (line, session_duration)
|
2015-07-03 01:03:05 +08:00
|
|
|
markup = {color: True, 'bold': True}
|
2013-08-05 15:45:10 +08:00
|
|
|
|
2010-11-01 02:51:16 +08:00
|
|
|
if self.verbosity >= 0:
|
2013-07-04 01:43:18 +08:00
|
|
|
self.write_sep("=", msg, **markup)
|
2013-07-06 21:43:59 +08:00
|
|
|
if self.verbosity == -1:
|
2013-08-05 15:45:10 +08:00
|
|
|
self.write_line(msg, **markup)
|
2009-02-27 18:18:27 +08:00
|
|
|
|
|
|
|
def summary_deselected(self):
|
|
|
|
if 'deselected' in self.stats:
|
2016-08-01 19:50:59 +08:00
|
|
|
self.write_sep("=", "%d tests deselected" % (
|
|
|
|
len(self.stats['deselected'])), bold=True)
|
2009-02-27 18:18:27 +08:00
|
|
|
|
2017-07-17 07:25:09 +08:00
|
|
|
|
2009-02-27 18:18:27 +08:00
|
|
|
def repr_pythonversion(v=None):
|
|
|
|
if v is None:
|
|
|
|
v = sys.version_info
|
|
|
|
try:
|
|
|
|
return "%s.%s.%s-%s-%s" % v
|
|
|
|
except (TypeError, ValueError):
|
|
|
|
return str(v)
|
|
|
|
|
2017-07-17 07:25:09 +08:00
|
|
|
|
2017-11-04 23:17:20 +08:00
|
|
|
def flatten(values):
|
|
|
|
for x in values:
|
2010-01-13 04:43:25 +08:00
|
|
|
if isinstance(x, (list, tuple)):
|
|
|
|
for y in flatten(x):
|
|
|
|
yield y
|
|
|
|
else:
|
|
|
|
yield x
|
2010-02-05 06:45:07 +08:00
|
|
|
|
2017-07-17 07:25:09 +08:00
|
|
|
|
2015-07-03 01:03:05 +08:00
|
|
|
def build_summary_stats_line(stats):
|
|
|
|
keys = ("failed passed skipped deselected "
|
2017-02-25 23:02:48 +08:00
|
|
|
"xfailed xpassed warnings error").split()
|
2015-07-01 07:20:25 +08:00
|
|
|
unknown_key_seen = False
|
2015-07-03 01:03:05 +08:00
|
|
|
for key in stats.keys():
|
|
|
|
if key not in keys:
|
2017-07-17 07:25:09 +08:00
|
|
|
if key: # setup/teardown reports have an empty key, ignore them
|
2015-07-01 07:06:28 +08:00
|
|
|
keys.append(key)
|
2015-07-01 07:20:25 +08:00
|
|
|
unknown_key_seen = True
|
2015-07-03 01:03:05 +08:00
|
|
|
parts = []
|
|
|
|
for key in keys:
|
2015-07-01 07:06:28 +08:00
|
|
|
val = stats.get(key, None)
|
|
|
|
if val:
|
2017-02-25 23:02:48 +08:00
|
|
|
parts.append("%d %s" % (len(val), key))
|
2015-11-30 23:59:12 +08:00
|
|
|
|
|
|
|
if parts:
|
|
|
|
line = ", ".join(parts)
|
|
|
|
else:
|
2015-12-01 00:32:20 +08:00
|
|
|
line = "no tests ran"
|
2015-07-03 01:03:05 +08:00
|
|
|
|
|
|
|
if 'failed' in stats or 'error' in stats:
|
|
|
|
color = 'red'
|
2015-07-01 07:20:25 +08:00
|
|
|
elif 'warnings' in stats or unknown_key_seen:
|
|
|
|
color = 'yellow'
|
2015-07-01 07:32:33 +08:00
|
|
|
elif 'passed' in stats:
|
2015-07-03 01:03:05 +08:00
|
|
|
color = 'green'
|
2015-07-01 07:32:33 +08:00
|
|
|
else:
|
|
|
|
color = 'yellow'
|
2015-07-03 01:03:05 +08:00
|
|
|
|
|
|
|
return (line, color)
|
2015-08-17 15:10:01 +08:00
|
|
|
|
|
|
|
|
|
|
|
def _plugin_nameversions(plugininfo):
|
2017-11-04 23:17:20 +08:00
|
|
|
values = []
|
2015-08-17 15:10:01 +08:00
|
|
|
for plugin, dist in plugininfo:
|
|
|
|
# gets us name and version!
|
|
|
|
name = '{dist.project_name}-{dist.version}'.format(dist=dist)
|
|
|
|
# questionable convenience, but it keeps things short
|
|
|
|
if name.startswith("pytest-"):
|
|
|
|
name = name[7:]
|
|
|
|
# we decided to print python package names
|
|
|
|
# they can have more than one plugin
|
2017-11-04 23:17:20 +08:00
|
|
|
if name not in values:
|
|
|
|
values.append(name)
|
|
|
|
return values
|