44"""
55from __future__ import absolute_import , division , print_function
66
7+ import itertools
78from _pytest .main import EXIT_OK , EXIT_TESTSFAILED , EXIT_INTERRUPTED , \
89 EXIT_USAGEERROR , EXIT_NOTESTSCOLLECTED
910import pytest
@@ -26,11 +27,11 @@ def pytest_addoption(parser):
2627 help = "show extra test summary info as specified by chars (f)ailed, "
2728 "(E)error, (s)skipped, (x)failed, (X)passed, "
2829 "(p)passed, (P)passed with output, (a)all except pP. "
29- "The pytest warnings are displayed at all times except when "
30- "--disable-pytest- warnings is set" )
31- group ._addoption ('--disable-pytest-warnings' , default = False ,
32- dest = 'disablepytestwarnings ' , action = 'store_true' ,
33- help = 'disable warnings summary, overrides -r w flag ' )
30+ "Warnings are displayed at all times except when "
31+ "--disable-warnings is set" )
32+ group ._addoption ('--disable-warnings' , '--disable- pytest-warnings' , default = False ,
33+ dest = 'disable_warnings ' , action = 'store_true' ,
34+ help = 'disable warnings summary' )
3435 group ._addoption ('-l' , '--showlocals' ,
3536 action = "store_true" , dest = "showlocals" , default = False ,
3637 help = "show locals in tracebacks (disabled by default)." )
@@ -59,9 +60,9 @@ def mywriter(tags, args):
5960def getreportopt (config ):
6061 reportopts = ""
6162 reportchars = config .option .reportchars
62- if not config .option .disablepytestwarnings and 'w' not in reportchars :
63+ if not config .option .disable_warnings and 'w' not in reportchars :
6364 reportchars += 'w'
64- elif config .option .disablepytestwarnings and 'w' in reportchars :
65+ elif config .option .disable_warnings and 'w' in reportchars :
6566 reportchars = reportchars .replace ('w' , '' )
6667 if reportchars :
6768 for char in reportchars :
@@ -82,13 +83,40 @@ def pytest_report_teststatus(report):
8283 letter = "f"
8384 return report .outcome , letter , report .outcome .upper ()
8485
86+
8587class WarningReport (object ):
88+ """
89+ Simple structure to hold warnings information captured by ``pytest_logwarning``.
90+ """
8691 def __init__ (self , code , message , nodeid = None , fslocation = None ):
92+ """
93+ :param code: unused
94+ :param str message: user friendly message about the warning
95+ :param str|None nodeid: node id that generated the warning (see ``get_location``).
96+ :param tuple|py.path.local fslocation:
97+ file system location of the source of the warning (see ``get_location``).
98+ """
8799 self .code = code
88100 self .message = message
89101 self .nodeid = nodeid
90102 self .fslocation = fslocation
91103
104+ def get_location (self , config ):
105+ """
106+ Returns the more user-friendly information about the location
107+ of a warning, or None.
108+ """
109+ if self .nodeid :
110+ return self .nodeid
111+ if self .fslocation :
112+ if isinstance (self .fslocation , tuple ) and len (self .fslocation ) == 2 :
113+ filename , linenum = self .fslocation
114+ relpath = py .path .local (filename ).relto (config .invocation_dir )
115+ return '%s:%d' % (relpath , linenum )
116+ else :
117+ return str (self .fslocation )
118+ return None
119+
92120
93121class TerminalReporter (object ):
94122 def __init__ (self , config , file = None ):
@@ -168,8 +196,6 @@ def pytest_internalerror(self, excrepr):
168196
169197 def pytest_logwarning (self , code , fslocation , message , nodeid ):
170198 warnings = self .stats .setdefault ("warnings" , [])
171- if isinstance (fslocation , tuple ):
172- fslocation = "%s:%d" % fslocation
173199 warning = WarningReport (code = code , fslocation = fslocation ,
174200 message = message , nodeid = nodeid )
175201 warnings .append (warning )
@@ -440,13 +466,21 @@ def getreports(self, name):
440466
441467 def summary_warnings (self ):
442468 if self .hasopt ("w" ):
443- warnings = self .stats .get ("warnings" )
444- if not warnings :
469+ all_warnings = self .stats .get ("warnings" )
470+ if not all_warnings :
445471 return
446- self .write_sep ("=" , "pytest-warning summary" )
447- for w in warnings :
448- self ._tw .line ("W%s %s %s" % (w .code ,
449- w .fslocation , w .message ))
472+
473+ grouped = itertools .groupby (all_warnings , key = lambda wr : wr .get_location (self .config ))
474+
475+ self .write_sep ("=" , "warnings summary" , yellow = True , bold = False )
476+ for location , warnings in grouped :
477+ self ._tw .line (str (location ) or '<undetermined location>' )
478+ for w in warnings :
479+ lines = w .message .splitlines ()
480+ indented = '\n ' .join (' ' + x for x in lines )
481+ self ._tw .line (indented )
482+ self ._tw .line ()
483+ self ._tw .line ('-- Docs: http://doc.pytest.org/en/latest/warnings.html' )
450484
451485 def summary_passes (self ):
452486 if self .config .option .tbstyle != "no" :
@@ -548,8 +582,7 @@ def flatten(l):
548582
549583def build_summary_stats_line (stats ):
550584 keys = ("failed passed skipped deselected "
551- "xfailed xpassed warnings error" ).split ()
552- key_translation = {'warnings' : 'pytest-warnings' }
585+ "xfailed xpassed warnings error" ).split ()
553586 unknown_key_seen = False
554587 for key in stats .keys ():
555588 if key not in keys :
@@ -560,8 +593,7 @@ def build_summary_stats_line(stats):
560593 for key in keys :
561594 val = stats .get (key , None )
562595 if val :
563- key_name = key_translation .get (key , key )
564- parts .append ("%d %s" % (len (val ), key_name ))
596+ parts .append ("%d %s" % (len (val ), key ))
565597
566598 if parts :
567599 line = ", " .join (parts )
0 commit comments