|
15 | 15 | # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, |
16 | 16 | # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. |
17 | 17 |
|
| 18 | +import configparser |
18 | 19 | import errno |
19 | 20 | import fileinput |
20 | 21 | import logging |
@@ -424,8 +425,8 @@ def inject_fixtures(self, caplog): |
424 | 425 |
|
425 | 426 | @pytest.fixture(autouse=True) |
426 | 427 | def save_restore_logging(self): |
427 | | - """Save logger state and try to restore it after all tests in |
428 | | - this class have finished. |
| 428 | + """Save logger state and try to restore it after each test |
| 429 | + has finished. |
429 | 430 |
|
430 | 431 | The primary test is testDictLoggerConfigViaJson which |
431 | 432 | can change the loggers and break tests that depend on caplog |
@@ -484,6 +485,18 @@ def save_restore_logging(self): |
484 | 485 | logging.shutdown() |
485 | 486 | reload(logging) |
486 | 487 |
|
| 488 | + def reset_logging(self): |
| 489 | + """https://til.tafkas.net/posts/-resetting-python-logging-before-running-tests/""" |
| 490 | + loggers = [logging.getLogger(name) for name in logging.root.manager.loggerDict] |
| 491 | + loggers.append(logging.getLogger()) |
| 492 | + for logger in loggers: |
| 493 | + handlers = logger.handlers[:] |
| 494 | + for handler in handlers: |
| 495 | + logger.removeHandler(handler) |
| 496 | + handler.close() |
| 497 | + logger.setLevel(logging.NOTSET) |
| 498 | + logger.propagate = True |
| 499 | + |
487 | 500 | backend = 'anydbm' |
488 | 501 |
|
489 | 502 | def setUp(self): |
@@ -1562,6 +1575,225 @@ def testDictLoggerConfigViaJson(self): |
1562 | 1575 | (log_config_filename,)) |
1563 | 1576 | self.assertEqual(output, target) |
1564 | 1577 |
|
| 1578 | + def testIniFileLoggerConfig(self): |
| 1579 | + |
| 1580 | + # good base test case |
| 1581 | + config1 = dedent(""" |
| 1582 | + [loggers] |
| 1583 | + keys=root,roundup,roundup.http,roundup.hyperdb,actions,schema,extension,detector |
| 1584 | +
|
| 1585 | + [logger_root] |
| 1586 | + #DEBUG, INFO, WARNING, ERROR, CRITICAL |
| 1587 | + #also for root only NOTSET (all) |
| 1588 | + level=DEBUG |
| 1589 | + handlers=basic |
| 1590 | +
|
| 1591 | + [logger_roundup] |
| 1592 | + #DEBUG, INFO, WARNING, ERROR, CRITICAL |
| 1593 | + #also for root only NOTSET (all) |
| 1594 | + level=DEBUG |
| 1595 | + handlers=rotate |
| 1596 | + qualname=roundup |
| 1597 | + propagate=0 |
| 1598 | +
|
| 1599 | + [logger_roundup.http] |
| 1600 | + level=INFO |
| 1601 | + handlers=rotate_weblog |
| 1602 | + qualname=roundup.http |
| 1603 | + propagate=0 |
| 1604 | +
|
| 1605 | + [logger_roundup.hyperdb] |
| 1606 | + level=WARNING |
| 1607 | + handlers=rotate |
| 1608 | + qualname=roundup.hyperdb |
| 1609 | + propagate=0 |
| 1610 | +
|
| 1611 | + [logger_actions] |
| 1612 | + #DEBUG, INFO, WARNING, ERROR, CRITICAL |
| 1613 | + #also for root only NOTSET (all) |
| 1614 | + level=DEBUG |
| 1615 | + handlers=rotate |
| 1616 | + qualname=actions |
| 1617 | + propagate=0 |
| 1618 | +
|
| 1619 | + [logger_detector] |
| 1620 | + #DEBUG, INFO, WARNING, ERROR, CRITICAL |
| 1621 | + #also for root only NOTSET (all) |
| 1622 | + level=DEBUG |
| 1623 | + handlers=rotate |
| 1624 | + qualname=detector |
| 1625 | + propagate=0 |
| 1626 | +
|
| 1627 | + [logger_schema] |
| 1628 | + level=DEBUG |
| 1629 | + handlers=rotate |
| 1630 | + qualname=schema |
| 1631 | + propagate=0 |
| 1632 | +
|
| 1633 | + [logger_extension] |
| 1634 | + level=DEBUG |
| 1635 | + handlers=rotate |
| 1636 | + qualname=extension |
| 1637 | + propagate=0 |
| 1638 | +
|
| 1639 | + [handlers] |
| 1640 | + keys=basic,rotate,rotate_weblog |
| 1641 | +
|
| 1642 | + [handler_basic] |
| 1643 | + class=StreamHandler |
| 1644 | + args=(sys.stderr,) |
| 1645 | + formatter=basic |
| 1646 | +
|
| 1647 | + [handler_rotate] |
| 1648 | + class=logging.handlers.RotatingFileHandler |
| 1649 | + args=('roundup.log','a', 512000, 2) |
| 1650 | + formatter=basic |
| 1651 | +
|
| 1652 | + [handler_rotate_weblog] |
| 1653 | + class=logging.handlers.RotatingFileHandler |
| 1654 | + args=('httpd.log','a', 512000, 2) |
| 1655 | + formatter=plain |
| 1656 | +
|
| 1657 | + [formatters] |
| 1658 | + keys=basic,plain |
| 1659 | +
|
| 1660 | + [formatter_basic] |
| 1661 | + format=%(asctime)s %(name)s:%(module)s.%(funcName)s,%(levelname)s: %(message)s |
| 1662 | + datefmt=%Y-%m-%d %H:%M:%S |
| 1663 | +
|
| 1664 | + [formatter_plain] |
| 1665 | + format=%(message)s |
| 1666 | + """) |
| 1667 | + |
| 1668 | + log_config_filename = os.path.join(self.instance.tracker_home, |
| 1669 | + "_test_log_config.ini") |
| 1670 | + |
| 1671 | + # happy path |
| 1672 | + with open(log_config_filename, "w") as log_config_file: |
| 1673 | + log_config_file.write(config1) |
| 1674 | + |
| 1675 | + self.db.config.LOGGING_CONFIG = "_test_log_config.ini" |
| 1676 | + |
| 1677 | + # verify we have a clean environment |
| 1678 | + self.assertEqual(len(logging.getLogger('roundup.http').handlers), 0) |
| 1679 | + |
| 1680 | + # always returns None |
| 1681 | + self.db.config.init_logging() |
| 1682 | + |
| 1683 | + # verify that logging loaded and handler is set |
| 1684 | + # default log config doesn't define handlers for roundup.http |
| 1685 | + self.assertEqual(len(logging.getLogger('roundup.http').handlers), 1) |
| 1686 | + self.reset_logging() |
| 1687 | + |
| 1688 | + # use undefined enumeration |
| 1689 | + test_config = config1.replace("=DEBUG\n", "=DEBUF\n") |
| 1690 | + with open(log_config_filename, "w") as log_config_file: |
| 1691 | + log_config_file.write(test_config) |
| 1692 | + |
| 1693 | + with self.assertRaises(configuration.LoggingConfigError) as cm: |
| 1694 | + config = self.db.config.init_logging() |
| 1695 | + |
| 1696 | + # verify that logging was reset |
| 1697 | + # default log config doesn't define handlers for roundup.http |
| 1698 | + self.assertEqual(len(logging.getLogger('roundup.http').handlers), 0) |
| 1699 | + |
| 1700 | + self.assertEqual( |
| 1701 | + cm.exception.args[0].replace(r'\\','\\'), |
| 1702 | + ('Error loading logging config from %s.\n\n' |
| 1703 | + " Unknown level: 'DEBUF'\n\n" |
| 1704 | + 'Inappropriate argument value (of correct type).\n' % |
| 1705 | + log_config_filename) |
| 1706 | + ) |
| 1707 | + self.reset_logging() |
| 1708 | + |
| 1709 | + |
| 1710 | + # add a syntax error "= foo" |
| 1711 | + test_config = config1.replace("=DEBUG\n", "=DEBUG\n=foo\n", 1) |
| 1712 | + with open(log_config_filename, "w") as log_config_file: |
| 1713 | + log_config_file.write(test_config) |
| 1714 | + |
| 1715 | + with self.assertRaises(configuration.LoggingConfigError) as cm: |
| 1716 | + config = self.db.config.init_logging() |
| 1717 | + |
| 1718 | + # verify that logging was reset |
| 1719 | + # default log config doesn't define handlers for roundup.http |
| 1720 | + self.assertEqual(len(logging.getLogger('roundup.http').handlers), 0) |
| 1721 | + |
| 1722 | + self.assertEqual( |
| 1723 | + cm.exception.args[0].replace(r'\\','\\'), |
| 1724 | + ("Error loading logging config from %(filename)s.\n\n" |
| 1725 | + " %(filename)s is invalid: Source contains parsing errors: " |
| 1726 | + "'%(filename)s'\n\t[line 9]: '=foo\\n'\n\n" |
| 1727 | + "Source contains parsing errors: '%(filename)s'\n" |
| 1728 | + "\t[line 9]: '=foo\\n' Unspecified run-time error.\n" % |
| 1729 | + {"filename": log_config_filename}) |
| 1730 | + ) |
| 1731 | + self.reset_logging() |
| 1732 | + |
| 1733 | + # handler = basic to handler = basi |
| 1734 | + test_config = config1.replace("handlers=basic\n", "handlers=basi\n", 1) |
| 1735 | + with open(log_config_filename, "w") as log_config_file: |
| 1736 | + log_config_file.write(test_config) |
| 1737 | + |
| 1738 | + with self.assertRaises(configuration.LoggingConfigError) as cm: |
| 1739 | + config = self.db.config.init_logging() |
| 1740 | + |
| 1741 | + # verify that logging was reset |
| 1742 | + # default log config doesn't define handlers for roundup.http |
| 1743 | + self.assertEqual(len(logging.getLogger('roundup.http').handlers), 0) |
| 1744 | + |
| 1745 | + self.assertEqual( |
| 1746 | + cm.exception.args[0].replace(r'\\','\\'), |
| 1747 | + ("Error loading logging config from %(filename)s.\n\n" |
| 1748 | + " basi\n\n" |
| 1749 | + "Mapping key not found. No section found with this name.\n" % |
| 1750 | + {"filename": log_config_filename}) |
| 1751 | + ) |
| 1752 | + self.reset_logging() |
| 1753 | + |
| 1754 | + # Change class to missing class |
| 1755 | + test_config = config1.replace("class=StreamHandler\n", |
| 1756 | + "class=SHAndler\n", 1) |
| 1757 | + with open(log_config_filename, "w") as log_config_file: |
| 1758 | + log_config_file.write(test_config) |
| 1759 | + |
| 1760 | + with self.assertRaises(configuration.LoggingConfigError) as cm: |
| 1761 | + config = self.db.config.init_logging() |
| 1762 | + |
| 1763 | + # verify that logging was reset |
| 1764 | + # default log config doesn't define handlers for roundup.http |
| 1765 | + self.assertEqual(len(logging.getLogger('roundup.http').handlers), 0) |
| 1766 | + |
| 1767 | + self.assertEqual( |
| 1768 | + cm.exception.args[0].replace(r'\\','\\'), |
| 1769 | + ("Error loading logging config from %(filename)s.\n\n" |
| 1770 | + " No module named 'SHAndler'\n\n" |
| 1771 | + "name 'SHAndler' is not defined Module not found.\n" % |
| 1772 | + {"filename": log_config_filename}) |
| 1773 | + ) |
| 1774 | + self.reset_logging() |
| 1775 | + |
| 1776 | + # remove section to cause duplicate option definition |
| 1777 | + test_config = config1.replace("[logger_roundup.http]\n", |
| 1778 | + "\n", 1) |
| 1779 | + with open(log_config_filename, "w") as log_config_file: |
| 1780 | + log_config_file.write(test_config) |
| 1781 | + |
| 1782 | + with self.assertRaises(configparser.DuplicateOptionError) as cm: |
| 1783 | + config = self.db.config.init_logging() |
| 1784 | + |
| 1785 | + # verify that logging was reset |
| 1786 | + # default log config doesn't define handlers for roundup.http |
| 1787 | + self.assertEqual(len(logging.getLogger('roundup.http').handlers), 0) |
| 1788 | + |
| 1789 | + self.assertEqual( |
| 1790 | + str(cm.exception).replace(r'\\','\\'), |
| 1791 | + ("While reading from '%(filename)s' [line 20]: " |
| 1792 | + "option 'level' in section 'logger_roundup' already exists" % |
| 1793 | + {"filename": log_config_filename}) |
| 1794 | + ) |
| 1795 | + self.reset_logging() |
| 1796 | + |
1565 | 1797 | def test_missing_logging_config_file(self): |
1566 | 1798 | saved_config = self.db.config['LOGGING_CONFIG'] |
1567 | 1799 |
|
|
0 commit comments