@@ -51,6 +51,10 @@ def pytest_addoption(parser):
5151 choices = ['yes' , 'no' , 'auto' ],
5252 help = "color terminal output (yes/no/auto)." )
5353
54+ parser .addini ("console_output_style" ,
55+ help = "console output: classic or with additional progress information (classic|progress)." ,
56+ default = 'progress' )
57+
5458
5559def pytest_configure (config ):
5660 config .option .verbose -= config .option .quiet
@@ -135,16 +139,20 @@ def __init__(self, config, file=None):
135139 self .showfspath = self .verbosity >= 0
136140 self .showlongtestinfo = self .verbosity > 0
137141 self ._numcollected = 0
142+ self ._session = None
138143
139144 self .stats = {}
140145 self .startdir = py .path .local ()
141146 if file is None :
142147 file = sys .stdout
143148 self ._writer = _pytest .config .create_terminal_writer (config , file )
149+ self ._screen_width = self .writer .fullwidth
144150 self .currentfspath = None
145151 self .reportchars = getreportopt (config )
146152 self .hasmarkup = self .writer .hasmarkup
147153 self .isatty = file .isatty ()
154+ self ._progress_items_reported = 0
155+ self ._show_progress_info = self .config .getini ('console_output_style' ) == 'progress'
148156
149157 @property
150158 def writer (self ):
@@ -163,6 +171,8 @@ def hasopt(self, char):
163171 def write_fspath_result (self , nodeid , res ):
164172 fspath = self .config .rootdir .join (nodeid .split ("::" )[0 ])
165173 if fspath != self .currentfspath :
174+ if self .currentfspath is not None :
175+ self ._write_progress_information_filling_space ()
166176 self .currentfspath = fspath
167177 fspath = self .startdir .bestrelpath (fspath )
168178 self .writer .line ()
@@ -177,6 +187,7 @@ def write_ensure_prefix(self, prefix, extra="", **kwargs):
177187 if extra :
178188 self .writer .write (extra , ** kwargs )
179189 self .currentfspath = - 2
190+ self ._write_progress_information_filling_space ()
180191
181192 def ensure_newline (self ):
182193 if self .currentfspath :
@@ -203,7 +214,7 @@ def rewrite(self, line, **markup):
203214 """
204215 erase = markup .pop ('erase' , False )
205216 if erase :
206- fill_count = self .writer .fullwidth - len (line )
217+ fill_count = self .writer .fullwidth - len (line ) - 1
207218 fill = ' ' * fill_count
208219 else :
209220 fill = ''
@@ -256,20 +267,25 @@ def pytest_runtest_logreport(self, report):
256267 rep = report
257268 res = self .config .hook .pytest_report_teststatus (report = rep )
258269 cat , letter , word = res
270+ if isinstance (word , tuple ):
271+ word , markup = word
272+ else :
273+ markup = None
259274 self .stats .setdefault (cat , []).append (rep )
260275 self ._tests_ran = True
261276 if not letter and not word :
262277 # probably passed setup/teardown
263278 return
279+ running_xdist = hasattr (rep , 'node' )
280+ self ._progress_items_reported += 1
264281 if self .verbosity <= 0 :
265- if not hasattr ( rep , 'node' ) and self .showfspath :
282+ if not running_xdist and self .showfspath :
266283 self .write_fspath_result (rep .nodeid , letter )
267284 else :
268285 self .writer .write (letter )
286+ self ._write_progress_if_past_edge ()
269287 else :
270- if isinstance (word , tuple ):
271- word , markup = word
272- else :
288+ if markup is None :
273289 if rep .passed :
274290 markup = {'green' : True }
275291 elif rep .failed :
@@ -279,17 +295,45 @@ def pytest_runtest_logreport(self, report):
279295 else :
280296 markup = {}
281297 line = self ._locationline (rep .nodeid , * rep .location )
282- if not hasattr ( rep , 'node' ) :
298+ if not running_xdist :
283299 self .write_ensure_prefix (line , word , ** markup )
284- # self.writer.write(word, **markup)
285300 else :
286301 self .ensure_newline ()
287- if hasattr (rep , 'node' ):
288- self .writer .write ("[%s] " % rep .node .gateway .id )
302+ self .writer .write ("[%s]" % rep .node .gateway .id )
303+ if self ._show_progress_info :
304+ self .writer .write (self ._get_progress_information_message () + " " , cyan = True )
305+ else :
306+ self .writer .write (' ' )
289307 self .writer .write (word , ** markup )
290308 self .writer .write (" " + line )
291309 self .currentfspath = - 2
292310
311+ def _write_progress_if_past_edge (self ):
312+ if not self ._show_progress_info :
313+ return
314+ last_item = self ._progress_items_reported == self ._session .testscollected
315+ if last_item :
316+ self ._write_progress_information_filling_space ()
317+ return
318+
319+ past_edge = self .writer .chars_on_current_line + self ._PROGRESS_LENGTH + 1 >= self ._screen_width
320+ if past_edge :
321+ msg = self ._get_progress_information_message ()
322+ self .writer .write (msg + '\n ' , cyan = True )
323+
324+ _PROGRESS_LENGTH = len (' [100%]' )
325+
326+ def _get_progress_information_message (self ):
327+ progress = self ._progress_items_reported * 100 // self ._session .testscollected
328+ return ' [{:3d}%]' .format (progress )
329+
330+ def _write_progress_information_filling_space (self ):
331+ if not self ._show_progress_info :
332+ return
333+ msg = self ._get_progress_information_message ()
334+ fill = ' ' * (self .writer .fullwidth - self .writer .chars_on_current_line - len (msg ) - 1 )
335+ self .write (fill + msg , cyan = True )
336+
293337 def pytest_collection (self ):
294338 if not self .isatty and self .config .option .verbose >= 1 :
295339 self .write ("collecting ... " , bold = True )
@@ -332,6 +376,7 @@ def pytest_collection_modifyitems(self):
332376
333377 @pytest .hookimpl (trylast = True )
334378 def pytest_sessionstart (self , session ):
379+ self ._session = session
335380 self ._sessionstarttime = time .time ()
336381 if not self .showheader :
337382 return
0 commit comments