@@ -1597,6 +1597,59 @@ def assert_log_level(
15971597 )
15981598
15991599
1600+ def assert_str_in_log (
1601+ records : list [tuple [int , str ]],
1602+ log_level_test : str ,
1603+ excludes_str : Optional [str ] = None ,
1604+ contains_str : Optional [str ] = None ,
1605+ ) -> None :
1606+ """Testing tool: Raises error if `excludes_str` appears , or `contains_str` doesn't appear at the test log level.
1607+ Unlike ``assert_log_level``, we don't raise error if the ``log_level_test`` is not present in the records.
1608+
1609+ Parameters
1610+ ----------
1611+ records : List[Tuple[int, str]]
1612+ List of (log_level: int, message: str) holding all of the captured logs.
1613+ log_level_test: str
1614+ String version of the log level for checking string (all uppercase).
1615+ excludes_str : str = None
1616+ If specified, errors if found in any of the log messages that are at level
1617+ ``log_level_test``.
1618+ contains_str : str = None
1619+ If specified, errors if not found in any of the log messages that are at level
1620+ ``log_level_test``.
1621+
1622+ Returns
1623+ -------
1624+ None
1625+ """
1626+
1627+ import sys
1628+
1629+ sys .stderr .write (str (records ) + "\n " )
1630+
1631+ # do nothing for None log level
1632+ if log_level_test is None :
1633+ return
1634+
1635+ log_level_test_int = _get_level_int (log_level_test )
1636+ contains_str_found = False
1637+ for log in records :
1638+ log_level , log_message = log
1639+ if log_level == log_level_test_int :
1640+ if excludes_str is not None and excludes_str in log_message :
1641+ raise AssertionError (
1642+ f"Log record at level '{ log_level_test } ' contained '{ excludes_str } '."
1643+ )
1644+ if contains_str is not None and contains_str in log_message :
1645+ contains_str_found = True
1646+
1647+ if contains_str and not contains_str_found :
1648+ raise AssertionError (
1649+ f"Log record at level '{ log_level_test } ' did not contain '{ contains_str } '."
1650+ )
1651+
1652+
16001653class AssertLogLevelHandler :
16011654 """Log handler used to store log records during assertion."""
16021655
@@ -1608,8 +1661,8 @@ def handle(self, level, level_name, message):
16081661
16091662
16101663@dataclasses .dataclass
1611- class AssertLogLevel :
1612- """Context manager to check log level for records logged within its context ."""
1664+ class AbstractAssertLog :
1665+ """Context manager to check logs ."""
16131666
16141667 log_level_expected : Union [str , None ]
16151668 contains_str : str = None
@@ -1630,6 +1683,11 @@ def __enter__(self):
16301683 td .log .handlers ["assert_log_level" ] = self .handler
16311684 return self
16321685
1686+
1687+ @dataclasses .dataclass
1688+ class AssertLogLevel (AbstractAssertLog ):
1689+ """Context manager to check log level for records logged within its context."""
1690+
16331691 def __exit__ (self , exc_type , exc_value , traceback ):
16341692 # Check the records and clean up
16351693 assert_log_level (
@@ -1641,6 +1699,24 @@ def __exit__(self, exc_type, exc_value, traceback):
16411699 del td .log .handlers ["assert_log_level" ]
16421700
16431701
1702+ @dataclasses .dataclass
1703+ class AssertLogStr (AbstractAssertLog ):
1704+ """Context manager to check if log contains certain strings at the test log level for records logged within its context."""
1705+
1706+ excludes_str : str = None
1707+
1708+ def __exit__ (self , exc_type , exc_value , traceback ):
1709+ # Check the records and clean up
1710+ assert_str_in_log (
1711+ records = self .records ,
1712+ log_level_test = self .log_level_expected ,
1713+ excludes_str = self .excludes_str ,
1714+ contains_str = self .contains_str ,
1715+ )
1716+ # Remove handler
1717+ del td .log .handlers ["assert_log_level" ]
1718+
1719+
16441720def get_test_root_dir ():
16451721 """return the root folder of test code"""
16461722
0 commit comments