88import pathlib
99import math
1010import datetime
11+ import typing
1112
1213import _pytest .outcomes
1314import _pytest .unittest
@@ -212,6 +213,12 @@ def helper__build_test_id(item: pytest.Function) -> str:
212213
213214return testID
214215
216+
217+ # /////////////////////////////////////////////////////////////////////////////
218+
219+ g_error_msg_count_key = pytest .StashKey [int ]()
220+ g_warning_msg_count_key = pytest .StashKey [int ]()
221+
215222# /////////////////////////////////////////////////////////////////////////////
216223
217224
@@ -285,6 +292,16 @@ def helper__makereport__call(
285292assert type (call )== pytest .CallInfo # noqa: E721
286293assert type (outcome )== pluggy .Result # noqa: E721
287294
295+ # --------
296+ item_error_msg_count = item .stash .get (g_error_msg_count_key ,0 )
297+ assert type (item_error_msg_count )== int # noqa: E721
298+ assert item_error_msg_count >= 0
299+
300+ item_warning_msg_count = item .stash .get (g_warning_msg_count_key ,0 )
301+ assert type (item_warning_msg_count )== int # noqa: E721
302+ assert item_warning_msg_count >= 0
303+
304+ # --------
288305rep = outcome .get_result ()
289306assert rep is not None
290307assert type (rep )== pytest .TestReport # noqa: E721
@@ -336,6 +353,7 @@ def helper__makereport__call(
336353reasonMsgTempl = "XFAIL REASON: {0}"
337354
338355logging .error (call .excinfo .value )
356+ item_error_msg_count += 1
339357
340358assert type (reasonText )== str # noqa: E721
341359
@@ -350,7 +368,13 @@ def helper__makereport__call(
350368
351369TEST_PROCESS_STATS .incrementFailedTestCount (testID )
352370
353- logging .error (call .excinfo .value )
371+ if type (call .excinfo .value )== SIGNAL_EXCEPTION :# noqa: E721
372+ assert item_error_msg_count > 0
373+ pass
374+ else :
375+ logging .error (call .excinfo .value )
376+ item_error_msg_count += 1
377+
354378exitStatus = "FAILED"
355379elif rep .outcome == "passed" :
356380assert call .excinfo is None
@@ -380,9 +404,11 @@ def helper__makereport__call(
380404
381405# --------
382406logging .info ("*" )
383- logging .info ("* DURATION : {0}" .format (timedelta_to_human_text (testDurration )))
407+ logging .info ("* DURATION : {0}" .format (timedelta_to_human_text (testDurration )))
384408logging .info ("*" )
385- logging .info ("* EXIT STATUS : {0}" .format (exitStatus ))
409+ logging .info ("* EXIT STATUS : {0}" .format (exitStatus ))
410+ logging .info ("* ERROR COUNT : {0}" .format (item_error_msg_count ))
411+ logging .info ("* WARNING COUNT: {0}" .format (item_warning_msg_count ))
386412logging .info ("*" )
387413logging .info ("* STOP TEST {0}" .format (testID ))
388414logging .info ("*" )
@@ -437,6 +463,186 @@ def pytest_runtest_makereport(item: pytest.Function, call: pytest.CallInfo):
437463# /////////////////////////////////////////////////////////////////////////////
438464
439465
466+ class LogErrorWrapper2 :
467+ _old_method :any
468+ _counter :typing .Optional [int ]
469+
470+ # --------------------------------------------------------------------
471+ def __init__ (self ):
472+ self ._old_method = None
473+ self ._counter = None
474+
475+ # --------------------------------------------------------------------
476+ def __enter__ (self ):
477+ assert self ._old_method is None
478+ assert self ._counter is None
479+
480+ self ._old_method = logging .error
481+ self ._counter = 0
482+
483+ logging .error = self
484+ return self
485+
486+ # --------------------------------------------------------------------
487+ def __exit__ (self ,exc_type ,exc_val ,exc_tb ):
488+ assert self ._old_method is not None
489+ assert self ._counter is not None
490+
491+ assert logging .error is self
492+
493+ logging .error = self ._old_method
494+
495+ self ._old_method = None
496+ self ._counter = None
497+ return False
498+
499+ # --------------------------------------------------------------------
500+ def __call__ (self ,* args ,** kwargs ):
501+ assert self ._old_method is not None
502+ assert self ._counter is not None
503+
504+ assert type (self ._counter )== int # noqa: E721
505+ assert self ._counter >= 0
506+
507+ r = self ._old_method (* args ,** kwargs )
508+
509+ self ._counter += 1
510+ assert self ._counter > 0
511+
512+ return r
513+
514+
515+ # /////////////////////////////////////////////////////////////////////////////
516+
517+
518+ class LogWarningWrapper2 :
519+ _old_method :any
520+ _counter :typing .Optional [int ]
521+
522+ # --------------------------------------------------------------------
523+ def __init__ (self ):
524+ self ._old_method = None
525+ self ._counter = None
526+
527+ # --------------------------------------------------------------------
528+ def __enter__ (self ):
529+ assert self ._old_method is None
530+ assert self ._counter is None
531+
532+ self ._old_method = logging .warning
533+ self ._counter = 0
534+
535+ logging .warning = self
536+ return self
537+
538+ # --------------------------------------------------------------------
539+ def __exit__ (self ,exc_type ,exc_val ,exc_tb ):
540+ assert self ._old_method is not None
541+ assert self ._counter is not None
542+
543+ assert logging .warning is self
544+
545+ logging .warning = self ._old_method
546+
547+ self ._old_method = None
548+ self ._counter = None
549+ return False
550+
551+ # --------------------------------------------------------------------
552+ def __call__ (self ,* args ,** kwargs ):
553+ assert self ._old_method is not None
554+ assert self ._counter is not None
555+
556+ assert type (self ._counter )== int # noqa: E721
557+ assert self ._counter >= 0
558+
559+ r = self ._old_method (* args ,** kwargs )
560+
561+ self ._counter += 1
562+ assert self ._counter > 0
563+
564+ return r
565+
566+
567+ # /////////////////////////////////////////////////////////////////////////////
568+
569+
570+ class SIGNAL_EXCEPTION (Exception ):
571+ def __init__ (self ):
572+ pass
573+
574+
575+ # /////////////////////////////////////////////////////////////////////////////
576+
577+
578+ @pytest .hookimpl (hookwrapper = True )
579+ def pytest_pyfunc_call (pyfuncitem :pytest .Function ):
580+ assert pyfuncitem is not None
581+ assert isinstance (pyfuncitem ,pytest .Function )
582+
583+ debug__log_error_method = logging .error
584+ assert debug__log_error_method is not None
585+
586+ debug__log_warning_method = logging .warning
587+ assert debug__log_warning_method is not None
588+
589+ pyfuncitem .stash [g_error_msg_count_key ]= 0
590+ pyfuncitem .stash [g_warning_msg_count_key ]= 0
591+
592+ try :
593+ with LogErrorWrapper2 ()as logErrorWrapper ,LogWarningWrapper2 ()as logWarningWrapper :
594+ assert type (logErrorWrapper )== LogErrorWrapper2 # noqa: E721
595+ assert logErrorWrapper ._old_method is not None
596+ assert type (logErrorWrapper ._counter )== int # noqa: E721
597+ assert logErrorWrapper ._counter == 0
598+ assert logging .error is logErrorWrapper
599+
600+ assert type (logWarningWrapper )== LogWarningWrapper2 # noqa: E721
601+ assert logWarningWrapper ._old_method is not None
602+ assert type (logWarningWrapper ._counter )== int # noqa: E721
603+ assert logWarningWrapper ._counter == 0
604+ assert logging .warning is logWarningWrapper
605+
606+ r :pluggy .Result = yield
607+
608+ assert r is not None
609+ assert type (r )== pluggy .Result # noqa: E721
610+
611+ assert logErrorWrapper ._old_method is not None
612+ assert type (logErrorWrapper ._counter )== int # noqa: E721
613+ assert logErrorWrapper ._counter >= 0
614+ assert logging .error is logErrorWrapper
615+
616+ assert logWarningWrapper ._old_method is not None
617+ assert type (logWarningWrapper ._counter )== int # noqa: E721
618+ assert logWarningWrapper ._counter >= 0
619+ assert logging .warning is logWarningWrapper
620+
621+ assert g_error_msg_count_key in pyfuncitem .stash
622+ assert g_warning_msg_count_key in pyfuncitem .stash
623+
624+ assert pyfuncitem .stash [g_error_msg_count_key ]== 0
625+ assert pyfuncitem .stash [g_warning_msg_count_key ]== 0
626+
627+ pyfuncitem .stash [g_error_msg_count_key ]= logErrorWrapper ._counter
628+ pyfuncitem .stash [g_warning_msg_count_key ]= logWarningWrapper ._counter
629+
630+ if r .exception is not None :
631+ pass
632+ elif logErrorWrapper ._counter == 0 :
633+ pass
634+ else :
635+ assert logErrorWrapper ._counter > 0
636+ r .force_exception (SIGNAL_EXCEPTION ())
637+ finally :
638+ assert logging .error is debug__log_error_method
639+ assert logging .warning is debug__log_warning_method
640+ pass
641+
642+
643+ # /////////////////////////////////////////////////////////////////////////////
644+
645+
440646def helper__calc_W (n :int )-> int :
441647assert n > 0
442648