def test_check_bad1(self):

        d = LCDictBasic(
            root_level='DEBUG',
            warnings=0)

        #  NOW SCREW IT UP:
        d.attach_root_filters('not-a-filter-1', 'not-a-filter-2')
        d.attach_root_handlers('not-a-handler-1', 'not-a-handler-2')
        d.add_file_handler(
            'fh', 'myfile.log',
            filters=['also-not-a-filter-1', 'also-not-a-filter-2'])

        # Swap stderr BEFORE d.check():
        _stderr = sys.stderr
        sio_err = io.StringIO()
        sys.stderr = sio_err

        # check out ".check()"
        # We expect this call to have written to stderr, and to raise KeyError
        with self.assertRaises(KeyError):
            d.check()

        self.assertEqual(
            sio_err.getvalue(),
            "Problems -- nonexistent things mentioned\n"
            "   handler            'fh' mentions     filter 'also-not-a-filter-1'\n"
            "   handler            'fh' mentions     filter 'also-not-a-filter-2'\n"
            "    logger              '' mentions     filter 'not-a-filter-1'\n"
            "    logger              '' mentions     filter 'not-a-filter-2'\n"
            "    logger              '' mentions    handler 'not-a-handler-1'\n"
            "    logger              '' mentions    handler 'not-a-handler-2'\n"
        )
        # unswap stderr, unnecessarily
        sys.stderr = _stderr
class TestLCD_NoWarnings(TestCase):
    class F():
        def filter(self, record):
            return True

    def setUp(self):
        "Subclass(es) may change .warning property"

        # Swap stderr, save existing:
        self._stderr = sys.stderr
        self.sio_err = io.StringIO()    # new "stderr"
        sys.stderr = self.sio_err
        # create an LCDictBasic that doesn't issue any warnings
        self.lcd = LCDictBasic(warnings=0)      # 0 == LCDictBasic.WARNINGS.NONE

    def tearDown(self):
        "."
        # restore
        sys.stderr = self._stderr

    def _verify_errmsg(self, ends=''):
        " utility method"
        errmsg = self.sio_err.getvalue()
        if self.lcd.warnings:
            self.assertEqual(
                errmsg.startswith("Warning") and errmsg.endswith(ends),
                True
            )
        else:
            self.assertEqual(errmsg, '')

    #-------------------
    # re-add
    #-------------------

    def test_warn_formatter_readd(self):

        self.lcd.add_formatter('my_formatter', format='%(message)s')
        # Now readd -- check for warning to stderr

        self.lcd.add_formatter('my_formatter', format='%(message)s')

        self._verify_errmsg(": redefinition of formatter 'my_formatter'.\n")
        self.assertEqual(list(self.lcd.formatters.keys()), ['my_formatter'])

    def test_warn_filter_readd(self):
        self.lcd.add_filter('my_filter', ** {'()': self.F})
        # Now readd -- check for warning to stderr
        self.lcd.add_filter('my_filter', ** {'()': self.F})

        self._verify_errmsg(": redefinition of filter 'my_filter'.\n")
        self.assertEqual(list(self.lcd.filters.keys()), ['my_filter'])

    def test_warn_handler_readd(self):
        self.lcd.add_handler(
                        'my_handler',
                        class_='logging.StreamHandler',
                        stream='sys.stdout')
        # Now readd -- check for warning to stderr
        self.lcd.add_handler(
                        'my_handler',
                        class_='logging.StreamHandler',
                        stream='sys.stdout')

        self._verify_errmsg(": redefinition of handler 'my_handler'.\n")
        self.assertEqual(list(self.lcd.handlers.keys()), ['my_handler'])

    def test_warn_logger_readd(self):
        self.lcd = LCDictBasic()
        self.lcd.add_logger('my_logger')
        # Now readd -- check for warning to stderr
        self.lcd.add_logger('my_logger')

        self._verify_errmsg(": redefinition of logger 'my_logger'.\n")
        self.assertEqual(list(self.lcd.loggers.keys()), ['my_logger'])

    #-------------------
    # handler/formatter
    #-------------------
    def test_add_handler_no_formatter__then_attach(self):
        self.lcd.add_formatter('f1', format='< a bad format string >')
        self.lcd.add_handler('my_handler')
        self.lcd.set_handler_formatter('my_handler', 'f1')

        self.assertEqual(self.lcd.handlers['my_handler']['formatter'],
                         'f1'
        )

    def test_warn_reattach_formatter(self):
        # attach same formatter twice
        self.lcd.add_formatter('f1',
                               format='%(message)s'
        ).add_handler('my_handler',
                      formatter='f1')
        # Now reattach -- check for warning to stderr
        self.lcd.set_handler_formatter('my_handler', 'f1')

        self._verify_errmsg(": formatter 'f1' already attached to handler 'my_handler'.\n")
        self.assertEqual(
            self.lcd.handlers['my_handler']['formatter'],
            'f1'
        )

    def test_warn_redefine_formatter(self):
        # attach two diff formatters
        self.lcd.add_formatter('f1',  format='%(message)s'
        ).add_formatter('f2',  format=''
        ).add_handler('my_handler',
                      formatter='f1')
        # Now attach f2 -- check for warning to stderr
        self.lcd.set_handler_formatter('my_handler', 'f2')

        self._verify_errmsg(": formatter 'f2' replaces 'f1' in handler 'my_handler'.\n")
        self.assertEqual(
            self.lcd.handlers['my_handler']['formatter'],
            'f2'
        )

    #====================================
    # test duplicates in add_* lists
    #      and attach_*_* reattachments
    #====================================

    #-------------------
    # handler/filters
    #-------------------

    def test_warn__add_handler__dup_filters(self):
                # add_handler -- dup filters in list
        self.lcd.add_filter('filter1', ** {'()': self.F}
        ).add_filter('filter2', ** {'()': self.F})
        self.lcd.add_handler('my_handler',
                             filters=['filter1', 'filter2', 'filter1', 'filter2'])

        self._verify_errmsg(": list of filters to attach to handler 'my_handler'"
                            " contains duplicates: 'filter1', 'filter2'.\n")
        self.assertEqual(
            self.lcd.handlers['my_handler']['filters'],
            ['filter1', 'filter2']
        )

    def test_warn__attach_handler_filters__dup_filters(self):
        self.lcd.add_filter('filter1', ** {'()': self.F}
        ).add_handler('my_handler'
        ).attach_handler_filters(
            'my_handler',
            'filter1', 'filter1'
        )
        self._verify_errmsg(": list of filters to attach to handler 'my_handler'"
                            " contains duplicates: 'filter1'.\n")
        self.assertEqual(
            self.lcd.handlers['my_handler']['filters'],
            ['filter1']
        )

    def test_warn__attach_handler_filters__reattach_filters(self):
        self.lcd.add_filter('filter1', ** {'()': self.F}
        ).add_handler('my_handler',
                      filters='filter1'
        ).attach_handler_filters(
            'my_handler',
            'filter1'
        )
        self._verify_errmsg(": these filters are already attached to handler 'my_handler'"
                            ": 'filter1'.\n")
        self.assertEqual(
            self.lcd.handlers['my_handler']['filters'],
            ['filter1']
        )

    def test_warn__attach_handler_filters__dup_filters_reattach_filters(self):
        self.lcd.add_filter('filter1', ** {'()': self.F}
        ).add_handler('my_handler',
                      filters='filter1'
        ).attach_handler_filters(
            'my_handler',
            'filter1', 'filter1'
        )
        errmsg = self.sio_err.getvalue()
        # print(errmsg)           # TODO COMMENT OUT

        if self.lcd.warnings:
            errmsg1, errmsg2 = errmsg.splitlines()
            self.assertEqual(
                (errmsg1.startswith("Warning (") and
                 errmsg1.endswith(": list of filters to attach to handler 'my_handler'"
                                  " contains duplicates: 'filter1'.")
                ),
                True
            )
            self.assertEqual(
                (errmsg2.startswith("Warning (") and
                 errmsg2.endswith(": these filters are already attached to handler 'my_handler'"
                                  ": 'filter1'.")
                ),
                True
            )
        else:
            self.assertEqual(errmsg, '')

        self.assertEqual(
            self.lcd.handlers['my_handler']['filters'],
            ['filter1']
        )

    #-------------------
    # logger/filters
    #-------------------

    def test_warn__add_logger__dup_filters(self):
                # add_handler -- dup filters in list
        self.lcd.add_filter('filter1', ** {'()': self.F}
        ).add_filter('filter2', ** {'()': self.F})
        self.lcd.add_logger('my_logger',
                            filters=['filter1', 'filter2', 'filter1', 'filter2'])
        self._verify_errmsg(": list of filters to attach to logger 'my_logger'"
                            " contains duplicates: 'filter1', 'filter2'.\n")
        self.assertEqual(
            self.lcd.loggers['my_logger']['filters'],
            ['filter1', 'filter2']
        )

    def test_warn__attach_logger_filters__dup_filters(self):
        self.lcd.add_filter('filter1', ** {'()': self.F}
        ).add_logger('my_logger'
        ).attach_logger_filters(
            'my_logger',
            'filter1', 'filter1'
        )
        self._verify_errmsg(": list of filters to attach to logger 'my_logger'"
                            " contains duplicates: 'filter1'.\n")
        self.assertEqual(
            self.lcd.loggers['my_logger']['filters'],
            ['filter1']
        )

    def test_warn__attach_logger_filters__reattach_filters(self):
        self.lcd.add_filter('filter1', ** {'()': self.F}
        ).add_logger('my_logger',
                     filters='filter1'
        ).attach_logger_filters(
            'my_logger',
            'filter1'
        )
        self._verify_errmsg(": these filters are already attached to logger 'my_logger'"
                            ": 'filter1'.\n")
        self.assertEqual(
            self.lcd.loggers['my_logger']['filters'],
            ['filter1']
        )

    #-------------------
    # logger/handlers
    #-------------------

    def test_warn__add_logger__dup_handlers(self):
                # add_handler -- dup filters in list
        self.lcd.add_handler('handler1'
        ).add_handler('handler2')
        self.lcd.add_logger('my_logger',
                            handlers=['handler1', 'handler2', 'handler1', 'handler2'])

        self._verify_errmsg(": list of handlers to attach to logger 'my_logger'"
                            " contains duplicates: 'handler1', 'handler2'.\n")
        self.assertEqual(
            self.lcd.loggers['my_logger']['handlers'],
            ['handler1', 'handler2']
        )

    def test_warn__attach_logger_handlers__dup_handlers(self):
        self.lcd.add_handler('handler1'
        ).add_logger('my_logger'
        ).attach_logger_handlers(
            'my_logger',
            'handler1', 'handler1'
        )

        self._verify_errmsg(": list of handlers to attach to logger 'my_logger'"
                            " contains duplicates: 'handler1'.\n")
        self.assertEqual(
            self.lcd.loggers['my_logger']['handlers'],
            ['handler1']
        )

    def test_warn__attach_logger_handlers__reattach_handlers(self):
        self.lcd.add_handler('handler1',
        ).add_logger('my_logger',
                     handlers='handler1'
        ).attach_logger_handlers(
            'my_logger',
            'handler1'
        )
        self._verify_errmsg(": these handlers are already attached to logger 'my_logger'"
                            ": 'handler1'.\n")
        self.assertEqual(
            self.lcd.loggers['my_logger']['handlers'],
            ['handler1']
        )

    #-------------------
    # root/filters
    #-------------------

    def test_warn__add_root__dup_filters(self):
                # add_handler -- dup filters in list
        self.lcd.add_filter('filter1', ** {'()': self.F}
        ).add_filter('filter2', ** {'()': self.F})
        self.lcd.attach_root_filters('filter1', 'filter2', 'filter1', 'filter2')

        self._verify_errmsg(": list of filters to attach to logger ''"
                            " contains duplicates: 'filter1', 'filter2'.\n")
        self.assertEqual(
            self.lcd.root['filters'],
            ['filter1', 'filter2']
        )

    def test_warn__attach_root_filters__dup_filters(self):
        self.lcd.add_filter('filter1', ** {'()': self.F}
        ).attach_logger_filters(
            '',
            'filter1', 'filter1'
        )

        self._verify_errmsg(": list of filters to attach to logger ''"
                            " contains duplicates: 'filter1'.\n")
        self.assertEqual(
            self.lcd.root['filters'],
            ['filter1']
        )

    def test_warn__attach_root_filters__reattach_filters(self):
        self.lcd.add_filter('filter1', ** {'()': self.F}
        ).attach_root_filters('filter1'
        ).attach_root_filters('filter1'
                              )
        self._verify_errmsg(": these filters are already attached to logger ''"
                            ": 'filter1'.\n")
        self.assertEqual(
            self.lcd.root['filters'],
            ['filter1']
        )

    #-------------------
    # root/handlers
    #-------------------

    def test_warn__attach_root_handlers__dup_handlers(self):
                # add_handler -- dup filters in list
        self.lcd.add_handler('handler1'
        ).add_handler('handler2')
        self.lcd.attach_root_handlers(
                            'handler1', 'handler2', 'handler1', 'handler2')

        self._verify_errmsg(": list of handlers to attach to logger ''"
                            " contains duplicates: 'handler1', 'handler2'.\n")
        self.assertEqual(
            self.lcd.root['handlers'],
            ['handler1', 'handler2']
        )

    def test_warn__attach_root_handlers__reattach_handlers(self):
        self.lcd.add_handler('handler1',
        ).attach_root_handlers('handler1'
        ).attach_root_handlers('handler1'
        )

        self._verify_errmsg(": these handlers are already attached to logger ''"
                            ": 'handler1'.\n")
        self.assertEqual(
            self.lcd.root['handlers'],
            ['handler1']
        )

    # Testing ATTACH_UNDEFINED -- which means testing ``_check_defined`` logic,
    # and that the correct args are passed to it by each of the 10 calls to it:
    """
    add_handler
        formatter   (undefined)
        filters     (one or more filters undefined. Similarly below)

    add_logger
        filters
        handlers

    set_handler_formatter
    attach_handler_filters

    attach_logger_filters
    attach_logger_handlers

    attach_root_filters
    attach_root_handlers
    """
    def test_warn_undef__add_handler__formatter(self):
        "with formatter undefined"
        self.lcd.add_handler('h', formatter='no-such-thing')

        self._verify_errmsg(": attaching undefined formatter 'no-such-thing'"
                            " to handler 'h'.\n")
        self.assertEqual(
            self.lcd.handlers['h']['formatter'],
            'no-such-thing'
        )

    def _add_filters_F1_F2_to_lcd(self):
        "Whenever you need a couple of filters,"
        class F1():
            def filter(self): return True
        class F2():
            def filter(self): return False

        self.lcd.add_filter('F1', ** {'()': F1})
        self.lcd.add_filter('F2', ** {'()': F2})

    def test_warn_undef__add_handler__filters(self):
        "with some filter undefined"
        self._add_filters_F1_F2_to_lcd()

        self.lcd.add_handler('h',
                             filters=['F1', 'no-such-filter', 'F2'])
        self._verify_errmsg(": attaching undefined filter 'no-such-filter'"
                            " to handler 'h'.\n")
        self.assertEqual(
            self.lcd.handlers['h']['filters'],
            ['F1', 'no-such-filter', 'F2']
        )

    def test_warn_undef__add_logger__filters(self):
        "with some filter undefined"
        self._add_filters_F1_F2_to_lcd()

        self.lcd.add_logger('my_logger',
                             filters=['no-such-filter', 'F1', 'F2'])
        self._verify_errmsg(": attaching undefined filter 'no-such-filter'"
                            " to logger 'my_logger'.\n")
        self.assertEqual(
            self.lcd.loggers['my_logger']['filters'],
            ['no-such-filter', 'F1', 'F2']
        )

    def test_warn_undef__add_logger__handlers(self):
        "with some handler undefined"
        self.lcd.add_handler('real-handler')
        self.lcd.add_logger('my_logger',
                            handlers=['real-handler', 'no-such-handler'])
        self._verify_errmsg(": attaching undefined handler 'no-such-handler'"
                            " to logger 'my_logger'.\n")
        self.assertEqual(
            self.lcd.loggers['my_logger']['handlers'],
            ['real-handler', 'no-such-handler']
        )

    def test_warn_undef__set_handler_formatter(self):
        "with formatter undefined"
        self.lcd.add_handler('h')
        self.lcd.set_handler_formatter('h', 'no-such-formatter')
        self._verify_errmsg(": attaching undefined formatter 'no-such-formatter'"
                            " to handler 'h'.\n")
        self.assertEqual(
            self.lcd.handlers['h']['formatter'],
            'no-such-formatter'
        )

    def test_warn_undef__attach_handler_filters(self):
        "with one or more filters undefined"
        self.lcd.add_handler('h')
        self.lcd.attach_handler_filters('h', 'no-such-filter')
        self._verify_errmsg(": attaching undefined filter 'no-such-filter'"
                            " to handler 'h'.\n")
        self.assertEqual(
            self.lcd.handlers['h']['filters'],
            ['no-such-filter']
        )

    def test_warn_undef__attach_logger_filters(self):
        "with one or more filters undefined"
        self._add_filters_F1_F2_to_lcd()

        self.lcd.add_logger('elle')
        self.lcd.attach_logger_filters('elle', 'no-such-filter', 'F1', 'F2')
        self._verify_errmsg(": attaching undefined filter 'no-such-filter'"
                            " to logger 'elle'.\n")
        self.assertEqual(
            self.lcd.loggers['elle']['filters'],
            ['no-such-filter', 'F1', 'F2']
        )

    def test_warn_undef__attach_logger_handlers(self):
        "with one or more handlers undefined"
        self.lcd.add_logger('elle')
        self.lcd.add_handler('h1') \
                .add_handler('h2')
        self.lcd.attach_logger_handlers('elle', 'h1', 'no-such-handler', 'h2')
        self._verify_errmsg(": attaching undefined handler 'no-such-handler'"
                            " to logger 'elle'.\n")
        self.assertEqual(
            self.lcd.loggers['elle']['handlers'],
            ['h1', 'no-such-handler', 'h2']
        )

    def test_warn_undef__attach_root_filters(self):
        "with one or more filters undefined"
        self._add_filters_F1_F2_to_lcd()

        self.lcd.attach_root_filters('no-such-filter1', 'F1', 'no-such-filter2')
        self._verify_errmsg(": attaching undefined filters 'no-such-filter1', 'no-such-filter2'"
                            " to logger ''.\n")
        self.assertEqual(
            self.lcd.root['filters'],
            ['no-such-filter1', 'F1', 'no-such-filter2']
        )

    def test_warn_undef__attach_root_handlers(self):
        "with one or more handlers undefined"
        self.lcd.add_handler('h1') \
                .add_handler('h2')
        self.lcd.attach_root_handlers('h1', 'h2', 'no-such-handler')
        self._verify_errmsg(": attaching undefined handler 'no-such-handler'"
                            " to logger ''.\n")
        self.assertEqual(
            self.lcd.root['handlers'],
            ['h1', 'h2', 'no-such-handler']
        )
    def test_filters_on_handler(self):

        class FilterOutOddRecords(logging.Filter):
            def filter(_self_, record):
                """If level of record is >= INFO, let record through;
                o/w, increment self.info_count += 1;
                if self.info_count odd, allow record through.
                :param self_: "self" for the CountInfo object (unused)
                :param record: logging.LogRecord
                :return: bool -- True ==> let record through, False ==> squelch
                """
                if record.levelno < logging.INFO:
                    return True
                self.info_count += 1
                if self.info_count % 2:
                    self.test_filters_on_handler__messages.append(self.info_count - 1)
                return self.info_count % 2

        lcd = LCDictBasic()
        lcd.set_root_level('DEBUG')

        lcd.add_filter(
            'filter_odd',
            ** {'()': FilterOutOddRecords}
        ).add_formatter(
            'msg',
            format='%(message)s'
        ).add_handler(
            'console',
            class_='logging.StreamHandler',     # writes messages
            # class_='logging.NullHandler',     # can't do this to suppress output: filter won't be called
            level='INFO',
            formatter='msg',
        )
        lcd.attach_handler_filters('console')               # coverage
        lcd.attach_handler_filters('console', 'filter_odd')
        lcd.attach_root_handlers('console')

        # Swap stderr BEFORE lcd.config:
        _stderr = sys.stderr
        sio_err = io.StringIO()
        sys.stderr = sio_err

        # NOW do lcd.config:
        lcd.config(disable_existing_loggers=False)

        # log stuff
        logger = logging.getLogger()
        logger.debug("Hi 0")
        logger.debug("Hi 1")
        if PY2:
            logger.info(u"0")     # passes filter
            logger.info(u"1")
            logger.info(u"2")     # passes filter
            logger.info(u"3")
        else:
            logger.info("0")     # passes filter
            logger.info("1")
            logger.info("2")     # passes filter
            logger.info("3")

        # unswap stderr, unnecessarily
        sys.stderr = _stderr
        # print("sio_err.getvalue(): '%s'" % sio_err.getvalue())

        self.assertEqual(sio_err.getvalue(), "0\n2\n")
        self.assertEqual(self.info_count, 4)
        self.assertEqual(self.test_filters_on_handler__messages, [0, 2])
    def test_filters_on_logger(self):

        def _count_debug(record):
            """
            :param record: logging.LogRecord
            :return: bool -- True ==> let record through, False ==> squelch
            """
            if record.levelno == logging.DEBUG:
                self.debug_count += 1
            return True

        class CountInfo(logging.Filter):
            def filter(self_, record):
                """
                :param self_: "self" for the CountInfo object (unused)
                :param record: logging.LogRecord
                :return: bool -- True ==> let record through, False ==> squelch
                """
                if record.levelno == logging.INFO:
                    self.info_count += 1
                return True

        if PY2:
            # Note: If Python 2.x, filter can't be just
            # .     any callable taking a logging record as arg;
            # .     it must have an attribute .filter
            # .     which must be a callable taking a LogRecord as arg.
            # .     Typically one does this:
            _count_debug.filter = _count_debug

        lcd = LCDictBasic()
        lcd.set_root_level('DEBUG')

        lcd.add_formatter(
            'msg',
            format='%(message)s'
        ).add_null_handler(
            'console',
            level='INFO',
        ).set_handler_level(
            'console', 'DEBUG')

        lcd.attach_root_handlers('console')

        lcd.add_filter(
            'count_d',
            ** {'()': lambda: _count_debug }
        ).add_filter(
            'count_i',
            ** {'()': CountInfo}
        )

        lcd.add_logger('h', filters=['count_d', 'count_i'])

        # shameless bid for more coverage
        lcd.set_logger_level('h', level='DEBUG')

        lcd.config(disable_existing_loggers=False)
        logger = logging.getLogger('h')

        if PY2:
            logger.debug(u"Hi 1")
            logger.debug(u"Hi 2")
            logger.info(u"Hi 3")
        else:
            logger.debug("Hi 1")
            logger.debug("Hi 2")
            logger.info("Hi 3")

        self.assertEqual(self.debug_count, 2)
        self.assertEqual(self.info_count, 1)