@@ -517,3 +517,39 @@ def test_redirect_stdout_unwrapped_after_keyboard_interrupt() -> None:
517517 f'stdout left wrapped after interrupt; rc={ result .returncode } \n '
518518 f'stderr={ result .stderr [- 500 :]} '
519519 )
520+
521+
522+ def test_wrap_logging_deduplicates_shared_handler (monkeypatch ) -> None :
523+ # A handler attached to more than one logger must be wrapped only once.
524+ # _iter_loggers yields the root logger then named loggers, so a handler
525+ # shared by both is seen twice and the second visit is skipped.
526+ for _ in range (5 ):
527+ progressbar .streams .unwrap (stderr = True , stdout = True )
528+ progressbar .streams .unwrap_logging ()
529+
530+ stream = io .StringIO ()
531+ monkeypatch .setattr (sys , 'stderr' , stream )
532+ monkeypatch .setattr (progressbar .streams , 'original_stderr' , stream )
533+ monkeypatch .setattr (progressbar .streams , 'stderr' , stream )
534+
535+ root = logging .getLogger ()
536+ named = logging .getLogger ('progressbar-test-shared-handler' )
537+ named .handlers = []
538+ named .propagate = False
539+ handler = logging .StreamHandler (sys .stderr )
540+ named .addHandler (handler )
541+ root .addHandler (handler ) # same handler object on two loggers
542+
543+ progressbar .streams .wrap_stderr ()
544+ try :
545+ progressbar .streams .wrap_logging ()
546+ # Recorded exactly once despite being reachable via two loggers.
547+ shared = [
548+ h for h , _ in progressbar .streams .logging_handlers if h is handler
549+ ]
550+ assert len (shared ) == 1
551+ finally :
552+ progressbar .streams .unwrap_logging ()
553+ progressbar .streams .unwrap (stderr = True )
554+ root .removeHandler (handler )
555+ named .handlers = []
0 commit comments