def watch_list(self): config_path = self.filesystem.dirname(self.filesystem.path_to_module('webkitpy.common.config')) watch_list_full_path = self.filesystem.join(config_path, 'watchlist') if not self.filesystem.exists(watch_list_full_path): raise Exception('Watch list file (%s) not found.' % watch_list_full_path) watch_list_contents = self.filesystem.read_text_file(watch_list_full_path) return WatchListParser().parse(watch_list_contents)
def check(self, lines): def log_to_style_error(message): # Always report line 0 since we don't have anything better. self._handle_style_error(0, 'watchlist/general', 5, message) WatchListParser(log_error=log_to_style_error).parse('\n'.join(lines))
def setUp(self): self._watch_list_parser = WatchListParser()
class WatchListTest(unittest.TestCase): def setUp(self): self._watch_list_parser = WatchListParser() def test_filename_definition_no_matches(self): watch_list = self._watch_list_parser.parse( '{' ' "DEFINITIONS": {' ' "WatchList1": {' ' "filename": r".*\\MyFileName\\.cpp",' ' },' ' },' ' "CC_RULES": {' ' "WatchList1": [' ' "*****@*****.**",' ' ],' ' },' '}') self.assertEqual(set([]), watch_list.find_matching_definitions(DIFF_TEST_DATA)) def test_filename_definition(self): watch_list = self._watch_list_parser.parse( '{' ' "DEFINITIONS": {' ' "WatchList1": {' ' "filename": r"WebCore/rendering/style/StyleFlexibleBoxData\.h",' ' },' ' },' ' "CC_RULES": {' ' "WatchList1": [' ' "*****@*****.**",' ' ],' ' },' '}') self.assertEqual(set(['WatchList1']), watch_list.find_matching_definitions(DIFF_TEST_DATA)) def test_cc_rules_simple(self): watch_list = self._watch_list_parser.parse( '{' ' "DEFINITIONS": {' ' "WatchList1": {' ' "filename": r"WebCore/rendering/style/StyleFlexibleBoxData\.h",' ' },' ' },' ' "CC_RULES": {' ' "WatchList1": [' ' "*****@*****.**",' ' ],' ' },' '}') cc_and_messages = watch_list.determine_cc_and_messages(DIFF_TEST_DATA) self.assertEqual({ 'cc_list': ['*****@*****.**'], 'messages': [], }, cc_and_messages) def test_cc_rules_complex(self): watch_list = self._watch_list_parser.parse( '{' ' "DEFINITIONS": {' ' "WatchList1": {' ' "filename": r"WebCore/rendering/style/StyleFlexibleBoxData\.h",' ' },' ' "WatchList2": {' ' "filename": r"WillNotMatch",' ' },' ' "WatchList3": {' ' "filename": r"WillNotMatch",' ' },' ' },' ' "CC_RULES": {' ' "WatchList2|WatchList1|WatchList3": [ "*****@*****.**", ],' ' },' '}') cc_and_messages = watch_list.determine_cc_and_messages(DIFF_TEST_DATA) self.assertEqual({ 'cc_list': ['*****@*****.**'], 'messages': [], }, cc_and_messages) def test_cc_and_message_rules_complex(self): watch_list = self._watch_list_parser.parse( '{' ' "DEFINITIONS": {' ' "WatchList1": {' ' "filename": r"WebCore/rendering/style/StyleFlexibleBoxData\.h",' ' },' ' "WatchList2": {' ' "filename": r"WillNotMatch",' ' },' ' "WatchList3": {' ' "filename": r"WillNotMatch",' ' },' ' },' ' "CC_RULES": {' ' "WatchList2|WatchList1|WatchList3": [ "*****@*****.**", ],' ' },' ' "MESSAGE_RULES": {' ' "WatchList2|WatchList1|WatchList3": [ "msg1", "msg2", ],' ' },' '}') cc_and_messages = watch_list.determine_cc_and_messages(DIFF_TEST_DATA) self.assertEqual({ 'cc_list': ['*****@*****.**'], 'messages': ['msg1', 'msg2'], }, cc_and_messages) def test_cc_and_message_rules_no_matches(self): watch_list = self._watch_list_parser.parse( '{' ' "DEFINITIONS": {' ' "WatchList1": {' ' "filename": r"WebCore/rendering/style/ThisFileDoesNotExist\.h",' ' },' ' "WatchList2": {' ' "filename": r"WillNotMatch",' ' },' ' "WatchList3": {' ' "filename": r"WillNotMatch",' ' },' ' },' ' "CC_RULES": {' ' "WatchList2|WatchList1|WatchList3": [ "*****@*****.**", ],' ' },' ' "MESSAGE_RULES": {' ' "WatchList2|WatchList1|WatchList3": [ "msg1", "msg2", ],' ' },' '}') cc_and_messages = watch_list.determine_cc_and_messages(DIFF_TEST_DATA) self.assertEqual({ 'cc_list': [], 'messages': [], }, cc_and_messages) def test_added_match(self): watch_list = self._watch_list_parser.parse( '{' ' "DEFINITIONS": {' ' "WatchList1": {' ' "in_added_lines": r"RenderStyle::initialBoxOrient",' ' },' ' "WatchList2": {' ' "in_deleted_lines": r"RenderStyle::initialBoxOrient",' ' },' ' },' ' "CC_RULES": {' ' "WatchList1": [ "*****@*****.**", ],' ' "WatchList2": [ "*****@*****.**", ],' ' },' '}') cc_and_messages = watch_list.determine_cc_and_messages(DIFF_TEST_DATA) self.assertEqual({ 'cc_list': ['*****@*****.**'], 'messages': [], }, cc_and_messages) def test_deleted_match(self): watch_list = self._watch_list_parser.parse( '{' ' "DEFINITIONS": {' ' "WatchList1": {' ' "in_added_lines": r"unsigned orient: 1;",' ' },' ' "WatchList2": {' ' "in_deleted_lines": r"unsigned orient: 1;",' ' },' ' },' ' "CC_RULES": {' ' "WatchList1": [ "*****@*****.**", ],' ' "WatchList2": [ "*****@*****.**", ],' ' },' '}') cc_and_messages = watch_list.determine_cc_and_messages(DIFF_TEST_DATA) self.assertEqual({ 'cc_list': ['*****@*****.**'], 'messages': [], }, cc_and_messages) def test_more_and_less_match(self): watch_list = self._watch_list_parser.parse( '{' ' "DEFINITIONS": {' ' "WatchList1": {' # This pattern is in both added and deleted lines, so no match. ' "more": r"userSelect == o\.userSelect",' ' },' ' "WatchList2": {' ' "more": r"boxOrient\(o\.boxOrient\)",' ' },' ' "WatchList3": {' ' "less": r"unsigned orient"' ' },' ' },' ' "CC_RULES": {' ' "WatchList1": [ "*****@*****.**", ],' ' "WatchList2": [ "*****@*****.**", ],' ' },' ' "MESSAGE_RULES": {' ' "WatchList3": ["Test message."],' ' },' '}') cc_and_messages = watch_list.determine_cc_and_messages(DIFF_TEST_DATA) self.assertEqual({ 'cc_list': ['*****@*****.**'], 'messages': ["Test message."], }, cc_and_messages) def test_complex_match(self): watch_list = self._watch_list_parser.parse( '{' ' "DEFINITIONS": {' ' "WatchList1": {' ' "filename": r"WebCore/rendering/style/StyleRareInheritedData\.cpp",' ' "in_added_lines": r"\&\& boxOrient == o\.boxOrient;",' ' "in_deleted_lines": r"\&\& userSelect == o\.userSelect;",' ' "more": r"boxOrient\(o\.boxOrient\)",' ' },' ' "WatchList2": {' ' "filename": r"WebCore/rendering/style/StyleRareInheritedData\.cpp",' ' "in_added_lines": r"RenderStyle::initialBoxOrient",' ' "less": r"userSelect;"' ' },' # WatchList3 won't match because these two patterns aren't in the same file. ' "WatchList3": {' ' "in_added_lines": r"RenderStyle::initialBoxOrient",' ' "in_deleted_lines": r"unsigned orient: 1;",' ' },' ' },' ' "CC_RULES": {' ' "WatchList1": [ "*****@*****.**", ],' ' "WatchList3": [ "*****@*****.**", ],' ' },' ' "MESSAGE_RULES": {' ' "WatchList2": ["This is a test message."],' ' },' '}') cc_and_messages = watch_list.determine_cc_and_messages(DIFF_TEST_DATA) self.assertEqual({ 'cc_list': ['*****@*****.**'], 'messages': ["This is a test message."], }, cc_and_messages)
def setUp(self): webkitunittest.TestCase.setUp(self) self._watch_list_parser = WatchListParser()
class WatchListTest(unittest.TestCase): def setUp(self): self._watch_list_parser = WatchListParser() def test_filename_definition_no_matches(self): watch_list = self._watch_list_parser.parse( "{" ' "DEFINITIONS": {' ' "WatchList1": {' ' "filename": r".*\\MyFileName\\.cpp",' " }," " }," ' "CC_RULES": {' ' "WatchList1": [' ' "*****@*****.**",' " ]," " }," "}" ) self.assertEqual(set([]), watch_list.find_matching_definitions(DIFF_TEST_DATA)) def test_filename_definition(self): watch_list = self._watch_list_parser.parse( "{" ' "DEFINITIONS": {' ' "WatchList1": {' ' "filename": r"WebCore/rendering/style/StyleFlexibleBoxData\.h",' " }," " }," ' "CC_RULES": {' ' "WatchList1": [' ' "*****@*****.**",' " ]," " }," "}" ) self.assertEqual(set(["WatchList1"]), watch_list.find_matching_definitions(DIFF_TEST_DATA)) def test_cc_rules_simple(self): watch_list = self._watch_list_parser.parse( "{" ' "DEFINITIONS": {' ' "WatchList1": {' ' "filename": r"WebCore/rendering/style/StyleFlexibleBoxData\.h",' " }," " }," ' "CC_RULES": {' ' "WatchList1": [' ' "*****@*****.**",' " ]," " }," "}" ) cc_and_messages = watch_list.determine_cc_and_messages(DIFF_TEST_DATA) self.assertEqual({"cc_list": ["*****@*****.**"], "messages": []}, cc_and_messages) def test_cc_rules_complex(self): watch_list = self._watch_list_parser.parse( "{" ' "DEFINITIONS": {' ' "WatchList1": {' ' "filename": r"WebCore/rendering/style/StyleFlexibleBoxData\.h",' " }," ' "WatchList2": {' ' "filename": r"WillNotMatch",' " }," ' "WatchList3": {' ' "filename": r"WillNotMatch",' " }," " }," ' "CC_RULES": {' ' "WatchList2|WatchList1|WatchList3": [ "*****@*****.**", ],' " }," "}" ) cc_and_messages = watch_list.determine_cc_and_messages(DIFF_TEST_DATA) self.assertEqual({"cc_list": ["*****@*****.**"], "messages": []}, cc_and_messages) def test_cc_and_message_rules_complex(self): watch_list = self._watch_list_parser.parse( "{" ' "DEFINITIONS": {' ' "WatchList1": {' ' "filename": r"WebCore/rendering/style/StyleFlexibleBoxData\.h",' " }," ' "WatchList2": {' ' "filename": r"WillNotMatch",' " }," ' "WatchList3": {' ' "filename": r"WillNotMatch",' " }," " }," ' "CC_RULES": {' ' "WatchList2|WatchList1|WatchList3": [ "*****@*****.**", ],' " }," ' "MESSAGE_RULES": {' ' "WatchList2|WatchList1|WatchList3": [ "msg1", "msg2", ],' " }," "}" ) cc_and_messages = watch_list.determine_cc_and_messages(DIFF_TEST_DATA) self.assertEqual({"cc_list": ["*****@*****.**"], "messages": ["msg1", "msg2"]}, cc_and_messages) def test_cc_and_message_rules_no_matches(self): watch_list = self._watch_list_parser.parse( "{" ' "DEFINITIONS": {' ' "WatchList1": {' ' "filename": r"WebCore/rendering/style/ThisFileDoesNotExist\.h",' " }," ' "WatchList2": {' ' "filename": r"WillNotMatch",' " }," ' "WatchList3": {' ' "filename": r"WillNotMatch",' " }," " }," ' "CC_RULES": {' ' "WatchList2|WatchList1|WatchList3": [ "*****@*****.**", ],' " }," ' "MESSAGE_RULES": {' ' "WatchList2|WatchList1|WatchList3": [ "msg1", "msg2", ],' " }," "}" ) cc_and_messages = watch_list.determine_cc_and_messages(DIFF_TEST_DATA) self.assertEqual({"cc_list": [], "messages": []}, cc_and_messages) def test_added_match(self): watch_list = self._watch_list_parser.parse( "{" ' "DEFINITIONS": {' ' "WatchList1": {' ' "in_added_lines": r"RenderStyle::initialBoxOrient",' " }," ' "WatchList2": {' ' "in_deleted_lines": r"RenderStyle::initialBoxOrient",' " }," " }," ' "CC_RULES": {' ' "WatchList1": [ "*****@*****.**", ],' ' "WatchList2": [ "*****@*****.**", ],' " }," "}" ) cc_and_messages = watch_list.determine_cc_and_messages(DIFF_TEST_DATA) self.assertEqual({"cc_list": ["*****@*****.**"], "messages": []}, cc_and_messages) def test_deleted_match(self): watch_list = self._watch_list_parser.parse( "{" ' "DEFINITIONS": {' ' "WatchList1": {' ' "in_added_lines": r"unsigned orient: 1;",' " }," ' "WatchList2": {' ' "in_deleted_lines": r"unsigned orient: 1;",' " }," " }," ' "CC_RULES": {' ' "WatchList1": [ "*****@*****.**", ],' ' "WatchList2": [ "*****@*****.**", ],' " }," "}" ) cc_and_messages = watch_list.determine_cc_and_messages(DIFF_TEST_DATA) self.assertEqual({"cc_list": ["*****@*****.**"], "messages": []}, cc_and_messages) def test_more_and_less_match(self): watch_list = self._watch_list_parser.parse( "{" ' "DEFINITIONS": {' ' "WatchList1": {' # This pattern is in both added and deleted lines, so no match. ' "more": r"userSelect == o\.userSelect",' " }," ' "WatchList2": {' ' "more": r"boxOrient\(o\.boxOrient\)",' " }," ' "WatchList3": {' ' "less": r"unsigned orient"' " }," " }," ' "CC_RULES": {' ' "WatchList1": [ "*****@*****.**", ],' ' "WatchList2": [ "*****@*****.**", ],' " }," ' "MESSAGE_RULES": {' ' "WatchList3": ["Test message."],' " }," "}" ) cc_and_messages = watch_list.determine_cc_and_messages(DIFF_TEST_DATA) self.assertEqual({"cc_list": ["*****@*****.**"], "messages": ["Test message."]}, cc_and_messages) def test_complex_match(self): watch_list = self._watch_list_parser.parse( "{" ' "DEFINITIONS": {' ' "WatchList1": {' ' "filename": r"WebCore/rendering/style/StyleRareInheritedData\.cpp",' ' "in_added_lines": r"\&\& boxOrient == o\.boxOrient;",' ' "in_deleted_lines": r"\&\& userSelect == o\.userSelect;",' ' "more": r"boxOrient\(o\.boxOrient\)",' " }," ' "WatchList2": {' ' "filename": r"WebCore/rendering/style/StyleRareInheritedData\.cpp",' ' "in_added_lines": r"RenderStyle::initialBoxOrient",' ' "less": r"userSelect;"' " }," # WatchList3 won't match because these two patterns aren't in the same file. ' "WatchList3": {' ' "in_added_lines": r"RenderStyle::initialBoxOrient",' ' "in_deleted_lines": r"unsigned orient: 1;",' " }," " }," ' "CC_RULES": {' ' "WatchList1": [ "*****@*****.**", ],' ' "WatchList3": [ "*****@*****.**", ],' " }," ' "MESSAGE_RULES": {' ' "WatchList2": ["This is a test message."],' " }," "}" ) cc_and_messages = watch_list.determine_cc_and_messages(DIFF_TEST_DATA) self.assertEqual({"cc_list": ["*****@*****.**"], "messages": ["This is a test message."]}, cc_and_messages)
class WatchListParserTest(webkitunittest.TestCase): def setUp(self): webkitunittest.TestCase.setUp(self) self._watch_list_parser = WatchListParser() def test_bad_section(self): watch_list = ('{"FOO": {}}') with OutputCapture(level=logging.INFO) as captured: self._watch_list_parser.parse(watch_list) self.assertEqual( captured.root.log.getvalue(), 'Unknown section "FOO" in watch list.\n', ) def test_section_typo(self): watch_list = ('{"DEFINTIONS": {}}') with OutputCapture(level=logging.INFO) as captured: self._watch_list_parser.parse(watch_list) self.assertEqual( captured.root.log.getvalue(), 'Unknown section "DEFINTIONS" in watch list.\n\nPerhaps it should be DEFINITIONS.\n', ) def test_bad_definition(self): watch_list = ('{' ' "DEFINITIONS": {' ' "WatchList1|A": {' ' "filename": r".*MyFileName\\.cpp",' ' },' ' },' '}') with OutputCapture(level=logging.INFO) as captured: self._watch_list_parser.parse(watch_list) self.assertEqual( captured.root.log.getvalue(), 'Invalid character "|" in definition "WatchList1|A".\n') def test_bad_filename_regex(self): watch_list = ('{' ' "DEFINITIONS": {' ' "WatchList1": {' ' "filename": r"*",' ' "more": r"RefCounted",' ' },' ' },' ' "CC_RULES": {' ' "WatchList1": ["*****@*****.**"],' ' },' '}') with OutputCapture(level=logging.INFO) as captured: self._watch_list_parser.parse(watch_list) if sys.version_info > (3, 0): expected_log = 'The regex "*" is invalid due to "nothing to repeat at position 0".\n' else: expected_log = 'The regex "*" is invalid due to "nothing to repeat".\n' self.assertEqual(captured.root.log.getvalue(), expected_log) def test_bad_more_regex(self): watch_list = ('{' ' "DEFINITIONS": {' ' "WatchList1": {' ' "filename": r"aFileName\\.cpp",' ' "more": r"*",' ' },' ' },' ' "CC_RULES": {' ' "WatchList1": ["*****@*****.**"],' ' },' '}') with OutputCapture(level=logging.INFO) as captured: self._watch_list_parser.parse(watch_list) if sys.version_info > (3, 0): expected_log = 'The regex "*" is invalid due to "nothing to repeat at position 0".\n' else: expected_log = 'The regex "*" is invalid due to "nothing to repeat".\n' self.assertEqual(captured.root.log.getvalue(), expected_log) def test_bad_match_type(self): watch_list = ( '{' ' "DEFINITIONS": {' ' "WatchList1": {' ' "nothing_matches_this": r".*MyFileName\\.cpp",' ' "filename": r".*MyFileName\\.cpp",' ' },' ' },' ' "CC_RULES": {' ' "WatchList1": ["*****@*****.**"],' ' },' '}') with OutputCapture(level=logging.INFO) as captured: self._watch_list_parser.parse(watch_list) self.assertEqual( captured.root.log.getvalue(), 'Unknown pattern type "nothing_matches_this" in definition "WatchList1".\n', ) def test_match_type_typo(self): watch_list = ('{' ' "DEFINITIONS": {' ' "WatchList1": {' ' "iflename": r".*MyFileName\\.cpp",' ' "more": r"RefCounted",' ' },' ' },' ' "CC_RULES": {' ' "WatchList1": ["*****@*****.**"],' ' },' '}') with OutputCapture(level=logging.INFO) as captured: self._watch_list_parser.parse(watch_list) self.assertEqual( captured.root.log.getvalue(), 'Unknown pattern type "iflename" in definition "WatchList1".\n\nPerhaps it should be filename.\n', ) def test_empty_definition(self): watch_list = ('{' ' "DEFINITIONS": {' ' "WatchList1": {' ' },' ' },' ' "CC_RULES": {' ' "WatchList1": ["*****@*****.**"],' ' },' '}') with OutputCapture(level=logging.INFO) as captured: self._watch_list_parser.parse(watch_list) self.assertEqual( captured.root.log.getvalue(), 'The definition "WatchList1" has no patterns, so it should be deleted.\n', ) def test_empty_cc_rule(self): watch_list = ('{' ' "DEFINITIONS": {' ' "WatchList1": {' ' "filename": r".*MyFileName\\.cpp",' ' },' ' },' ' "CC_RULES": {' ' "WatchList1": [],' ' },' '}') with OutputCapture(level=logging.INFO) as captured: self._watch_list_parser.parse(watch_list) self.assertEqual( captured.root.log.getvalue(), 'A rule for definition "WatchList1" is empty, so it should be deleted.\nThe following definitions are not ' 'used and should be removed: WatchList1\n', ) def test_cc_rule_with_invalid_email(self): watch_list = ('{' ' "DEFINITIONS": {' ' "WatchList1": {' ' "filename": r".*MyFileName\\.cpp",' ' },' ' },' ' "CC_RULES": {' ' "WatchList1": ["*****@*****.**"],' ' },' '}') with OutputCapture(level=logging.INFO) as captured: self._watch_list_parser.parse(watch_list) self.assertEqual( captured.root.log.getvalue(), 'The email alias [email protected] which is in the watchlist is not listed as a contributor in ' 'contributors.json\n', ) def test_cc_rule_with_secondary_email(self): # FIXME: We should provide a mock of CommitterList so that we can test this on fake data. watch_list = ('{' ' "DEFINITIONS": {' ' "WatchList1": {' ' "filename": r".*MyFileName\\.cpp",' ' },' ' },' ' "CC_RULES": {' ' "WatchList1": ["*****@*****.**"],' ' },' '}') with OutputCapture(level=logging.INFO) as captured: self._watch_list_parser.parse(watch_list) self.assertEqual(captured.root.log.getvalue(), '') def test_empty_message_rule(self): watch_list = ('{' ' "DEFINITIONS": {' ' "WatchList1": {' ' "filename": r".*MyFileName\\.cpp",' ' },' ' },' ' "MESSAGE_RULES": {' ' "WatchList1": [' ' ],' ' },' '}') with OutputCapture(level=logging.INFO) as captured: self._watch_list_parser.parse(watch_list) self.assertEqual( captured.root.log.getvalue(), 'A rule for definition "WatchList1" is empty, so it should be deleted.\nThe following definitions are not ' 'used and should be removed: WatchList1\n', ) def test_unused_defintion(self): watch_list = ('{' ' "DEFINITIONS": {' ' "WatchList1": {' ' "filename": r".*MyFileName\\.cpp",' ' },' ' },' '}') with OutputCapture(level=logging.INFO) as captured: self._watch_list_parser.parse(watch_list) self.assertEqual( captured.root.log.getvalue(), 'The following definitions are not used and should be removed: WatchList1\n', ) def test_cc_rule_with_undefined_defintion(self): watch_list = ('{' ' "CC_RULES": {' ' "WatchList1": ["*****@*****.**"]' ' },' '}') with OutputCapture(level=logging.INFO) as captured: self._watch_list_parser.parse(watch_list) self.assertEqual( captured.root.log.getvalue(), 'In section "CC_RULES", the following definitions are not used and should be removed: WatchList1\n', ) def test_message_rule_with_undefined_defintion(self): watch_list = ('{' ' "MESSAGE_RULES": {' ' "WatchList1": ["The message."]' ' },' '}') with OutputCapture(level=logging.INFO) as captured: self._watch_list_parser.parse(watch_list) self.assertEqual( captured.root.log.getvalue(), 'In section "MESSAGE_RULES", the following definitions are not used and should be removed: WatchList1\n', ) def test_cc_rule_with_undefined_defintion_with_suggestion(self): watch_list = ('{' ' "DEFINITIONS": {' ' "WatchList1": {' ' "filename": r".*MyFileName\\.cpp",' ' },' ' },' ' "CC_RULES": {' ' "WatchList": ["*****@*****.**"]' ' },' ' "MESSAGE_RULES": {' ' "WatchList1": ["*****@*****.**"]' ' },' '}') with OutputCapture(level=logging.INFO) as captured: self._watch_list_parser.parse(watch_list) self.assertEqual( captured.root.log.getvalue(), 'In section "CC_RULES", the following definitions are not used and should be removed: ' 'WatchList\n\nPerhaps it should be WatchList1.\n', ) def test_cc_rule_with_complex_logic(self): watch_list = ( '{' ' "DEFINITIONS": {' ' "WatchList1": {' ' "filename": r".*MyFileName\\.cpp",' ' },' ' "WatchList2": {' ' "filename": r".*MyFileName\\.h",' ' },' ' "WatchList3": {' ' "filename": r".*MyFileName\\.o",' ' },' ' },' ' "CC_RULES": {' ' "!WatchList1&!WatchList2|!WatchList3&!WatchListUndefined": ["*****@*****.**"]' ' },' ' "MESSAGE_RULES": {' ' "!WatchList1|WatchList2&!WatchList3|!WatchListUndefined": ["*****@*****.**"]' ' },' '}') with OutputCapture(level=logging.INFO) as captured: self._watch_list_parser.parse(watch_list) self.assertEqual( captured.root.log.getvalue(), '''In section "CC_RULES", the following definitions are not used and should be removed: WatchListUndefined Perhaps it should be WatchList3 or WatchList2 or WatchList1. In section "MESSAGE_RULES", the following definitions are not used and should be removed: WatchListUndefined Perhaps it should be WatchList3 or WatchList2 or WatchList1. ''', )