@@ -43,7 +43,7 @@ def cap_max_workers_in_pool(max_workers, is_browser):
4343 return max_workers
4444
4545
46- def run_test (test , allowed_failures_counter , lock , progress_counter , num_tests ):
46+ def run_test (test , allowed_failures_counter , lock , progress_counter , num_tests , buffer ):
4747 # If we have exceeded the number of allowed failures during the test run,
4848 # abort executing further tests immediately.
4949 if allowed_failures_counter and allowed_failures_counter .value < 0 :
@@ -54,8 +54,43 @@ def test_failed():
5454 with lock :
5555 allowed_failures_counter .value -= 1
5656
57+ start_time = time .perf_counter ()
58+
59+ def compute_progress ():
60+ if not lock :
61+ return ''
62+ with lock :
63+ val = f'[{ int (progress_counter .value * 100 / num_tests )} %] '
64+ progress_counter .value += 1
65+ return with_color (CYAN , val )
66+
67+ def printResult (res ):
68+ elapsed = time .perf_counter () - start_time
69+ progress = compute_progress ()
70+ if res .test_result == 'success' :
71+ msg = f'ok ({ elapsed :.2f} s)'
72+ errlog (f'{ progress } { res .test } ... { with_color (GREEN , msg )} ' )
73+ elif res .test_result == 'errored' :
74+ msg = f'{ res .test } ... ERROR'
75+ errlog (f'{ progress } { with_color (RED , msg )} ' )
76+ elif res .test_result == 'failed' :
77+ msg = f'{ res .test } ... FAIL'
78+ errlog (f'{ progress } { with_color (RED , msg )} ' )
79+ elif res .test_result == 'skipped' :
80+ msg = f"skipped '{ res .buffered_result .reason } '"
81+ errlog (f"{ progress } { res .test } ... { with_color (CYAN , msg )} " )
82+ elif res .test_result == 'unexpected success' :
83+ msg = f'unexpected success ({ elapsed :.2f} s)'
84+ errlog (f'{ progress } { res .test } ... { with_color (RED , msg )} ' )
85+ elif res .test_result == 'expected failure' :
86+ msg = f'expected failure ({ elapsed :.2f} s)'
87+ errlog (f'{ progress } { res .test } ... { with_color (RED , msg )} ' )
88+ else :
89+ assert False
90+
5791 olddir = os .getcwd ()
58- result = BufferedParallelTestResult (lock , progress_counter , num_tests )
92+ result = BufferedParallelTestResult ()
93+ result .buffer = buffer
5994 temp_dir = tempfile .mkdtemp (prefix = 'emtest_' )
6095 test .set_temp_dir (temp_dir )
6196 try :
@@ -72,6 +107,9 @@ def test_failed():
72107 except Exception as e :
73108 result .addError (test , e )
74109 test_failed ()
110+ finally :
111+ printResult (result )
112+
75113 # Before attempting to delete the tmp dir make sure the current
76114 # working directory is not within it.
77115 os .chdir (olddir )
@@ -139,7 +177,7 @@ def run(self, result):
139177 allowed_failures_counter = manager .Value ('i' , self .max_failures )
140178 progress_counter = manager .Value ('i' , 0 )
141179 lock = manager .Lock ()
142- results = pool .starmap (run_test , ((t , allowed_failures_counter , lock , progress_counter , len (tests )) for t in tests ), chunksize = 1 )
180+ results = pool .starmap (run_test , ((t , allowed_failures_counter , lock , progress_counter , len (tests ), result . buffer ) for t in tests ), chunksize = 1 )
143181 # Send a task to each worker to tear down the browser and server. This
144182 # relies on the implementation detail in the worker pool that all workers
145183 # are cycled through once.
@@ -233,21 +271,15 @@ def combine_results(self, result, buffered_results):
233271 return result
234272
235273
236- class BufferedParallelTestResult :
274+ class BufferedParallelTestResult ( unittest . TestResult ) :
237275 """A picklable struct used to communicate test results across processes
238-
239- Fulfills the interface for unittest.TestResult
240276 """
241- def __init__ (self , lock , progress_counter , num_tests ):
277+ def __init__ (self ):
278+ super ().__init__ ()
242279 self .buffered_result = None
243280 self .test_duration = 0
244281 self .test_result = 'errored'
245282 self .test_name = ''
246- self .lock = lock
247- self .progress_counter = progress_counter
248- self .num_tests = num_tests
249- self .failures = []
250- self .errors = []
251283
252284 @property
253285 def test (self ):
@@ -261,9 +293,6 @@ def test_short_name(self):
261293 def addDuration (self , test , elapsed ):
262294 self .test_duration = elapsed
263295
264- def calculateElapsed (self ):
265- return time .perf_counter () - self .start_time
266-
267296 def updateResult (self , result ):
268297 result .startTest (self .test )
269298 self .buffered_result .updateResult (result )
@@ -295,59 +324,49 @@ def log_test_run_for_visualization(self):
295324 prof .write (f',\n {{"pid":{ dummy_test_task_counter } ,"op":"exit","time":{ self .start_time + self .test_duration } ,"returncode":0}}' )
296325
297326 def startTest (self , test ):
327+ super ().startTest (test )
298328 self .test_name = str (test )
299- self .start_time = time .perf_counter ()
300329
301330 def stopTest (self , test ):
331+ super ().stopTest (test )
302332 # TODO(sbc): figure out a way to display this duration information again when
303333 # these results get passed back to the TextTestRunner/TextTestResult.
304334 self .buffered_result .duration = self .test_duration
305-
306- def compute_progress (self ):
307- if not self .lock :
308- return ''
309- with self .lock :
310- val = f'[{ int (self .progress_counter .value * 100 / self .num_tests )} %] '
311- self .progress_counter .value += 1
312- return with_color (CYAN , val )
335+ # Once we are done running the test and any stdout/stderr buffering has
336+ # being taking care or, we delete these fields which the parent class uses.
337+ # This is because they are not picklable (serializable).
338+ del self ._original_stdout
339+ del self ._original_stderr
313340
314341 def addSuccess (self , test ):
315- msg = f'ok ({ self .calculateElapsed ():.2f} s)'
316- errlog (f'{ self .compute_progress ()} { test } ... { with_color (GREEN , msg )} ' )
342+ super ().addSuccess (test )
317343 self .buffered_result = BufferedTestSuccess (test )
318344 self .test_result = 'success'
319345
320346 def addExpectedFailure (self , test , err ):
321- msg = f'expected failure ({ self .calculateElapsed ():.2f} s)'
322- errlog (f'{ self .compute_progress ()} { test } ... { with_color (RED , msg )} ' )
347+ super ().addExpectedFailure (test , err )
323348 self .buffered_result = BufferedTestExpectedFailure (test , err )
324349 self .test_result = 'expected failure'
325350
326351 def addUnexpectedSuccess (self , test ):
327- msg = f'unexpected success ({ self .calculateElapsed ():.2f} s)'
328- errlog (f'{ self .compute_progress ()} { test } ... { with_color (RED , msg )} ' )
352+ super ().addUnexpectedSuccess (test )
329353 self .buffered_result = BufferedTestUnexpectedSuccess (test )
330354 self .test_result = 'unexpected success'
331355
332356 def addSkip (self , test , reason ):
333- msg = f"skipped '{ reason } '"
334- errlog (f"{ self .compute_progress ()} { test } ... { with_color (CYAN , msg )} " )
357+ super ().addSkip (test , reason )
335358 self .buffered_result = BufferedTestSkip (test , reason )
336359 self .test_result = 'skipped'
337360
338361 def addFailure (self , test , err ):
339- msg = f'{ test } ... FAIL'
340- errlog (f'{ self .compute_progress ()} { with_color (RED , msg )} ' )
362+ super ().addFailure (test , err )
341363 self .buffered_result = BufferedTestFailure (test , err )
342364 self .test_result = 'failed'
343- self .failures += [test ]
344365
345366 def addError (self , test , err ):
346- msg = f'{ test } ... ERROR'
347- errlog (f'{ self .compute_progress ()} { with_color (RED , msg )} ' )
367+ super ().addError (test , err )
348368 self .buffered_result = BufferedTestError (test , err )
349369 self .test_result = 'errored'
350- self .errors += [test ]
351370
352371
353372class BufferedTestBase :
0 commit comments