8
8
import pathlib
9
9
import math
10
10
import datetime
11
+ import typing
11
12
12
13
import _pytest .outcomes
13
14
import _pytest .unittest
@@ -212,6 +213,12 @@ def helper__build_test_id(item: pytest.Function) -> str:
212
213
213
214
return testID
214
215
216
+
217
+ # /////////////////////////////////////////////////////////////////////////////
218
+
219
+ g_error_msg_count_key = pytest .StashKey [int ]()
220
+ g_warning_msg_count_key = pytest .StashKey [int ]()
221
+
215
222
# /////////////////////////////////////////////////////////////////////////////
216
223
217
224
@@ -285,6 +292,16 @@ def helper__makereport__call(
285
292
assert type (call )== pytest .CallInfo # noqa: E721
286
293
assert type (outcome )== pluggy .Result # noqa: E721
287
294
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
+ # --------
288
305
rep = outcome .get_result ()
289
306
assert rep is not None
290
307
assert type (rep )== pytest .TestReport # noqa: E721
@@ -336,6 +353,7 @@ def helper__makereport__call(
336
353
reasonMsgTempl = "XFAIL REASON: {0}"
337
354
338
355
logging .error (call .excinfo .value )
356
+ item_error_msg_count += 1
339
357
340
358
assert type (reasonText )== str # noqa: E721
341
359
@@ -350,7 +368,13 @@ def helper__makereport__call(
350
368
351
369
TEST_PROCESS_STATS .incrementFailedTestCount (testID )
352
370
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
+
354
378
exitStatus = "FAILED"
355
379
elif rep .outcome == "passed" :
356
380
assert call .excinfo is None
@@ -380,9 +404,11 @@ def helper__makereport__call(
380
404
381
405
# --------
382
406
logging .info ("*" )
383
- logging .info ("* DURATION : {0}" .format (timedelta_to_human_text (testDurration )))
407
+ logging .info ("* DURATION : {0}" .format (timedelta_to_human_text (testDurration )))
384
408
logging .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 ))
386
412
logging .info ("*" )
387
413
logging .info ("* STOP TEST {0}" .format (testID ))
388
414
logging .info ("*" )
@@ -437,6 +463,186 @@ def pytest_runtest_makereport(item: pytest.Function, call: pytest.CallInfo):
437
463
# /////////////////////////////////////////////////////////////////////////////
438
464
439
465
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
+
440
646
def helper__calc_W (n :int )-> int :
441
647
assert n > 0
442
648