1# 2# Copyright (C) 2018 Martin Owens 3# 4# This library is free software; you can redistribute it and/or 5# modify it under the terms of the GNU Lesser General Public 6# License as published by the Free Software Foundation; either 7# version 3.0 of the License, or (at your option) any later version. 8# 9# This library is distributed in the hope that it will be useful, 10# but WITHOUT ANY WARRANTY; without even the implied warranty of 11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12# Lesser General Public License for more details. 13# 14# You should have received a copy of the GNU Lesser General Public 15# License along with this library. 16# 17""" 18Provide some utilities to tests 19""" 20from logging import Handler, getLogger, root 21from collections import defaultdict 22 23# FLAG: do not report failures from here in tracebacks 24# pylint: disable=invalid-name 25__unittest = True 26 27class LoggingRecorder(Handler): 28 """Record any logger output for testing""" 29 def __init__(self, *args, **kwargs): 30 self.logs = defaultdict(list) 31 super(LoggingRecorder, self).__init__(*args, **kwargs) 32 33 def __getitem__(self, name): 34 return self.logs[name.upper()] 35 36 def emit(self, record): 37 """Save the log message to the right level""" 38 # We have no idea why record's getMessage is prefixed 39 msg = str(record.getMessage()) 40 if msg.startswith('u"'): 41 msg = msg[1:] 42 if msg and (msg[0] == msg[-1] and msg[0] in '"\''): 43 msg = msg[1:-1] 44 self[record.levelname].append(msg) 45 return True 46 47 48class LoggingMixin(object): 49 """Provide logger capture""" 50 log_name = None 51 52 def setUp(self): 53 """Make a fresh logger for each test function""" 54 super(LoggingMixin, self).setUp() 55 named = getLogger(self.log_name) 56 for handler in root.handlers[:]: 57 root.removeHandler(handler) 58 for handler in named.handlers[:]: 59 named.removeHandler(handler) 60 self.log_handler = LoggingRecorder(level='DEBUG') 61 named.addHandler(self.log_handler) 62 63 def tearDown(self): 64 """Warn about untested logs""" 65 for level in self.log_handler.logs: 66 for msg in self.log_handler[level]: 67 raise ValueError("Uncaught log: {}: {}\n".format(level, msg)) 68 69 def assertLog(self, level, msg): 70 """Checks that the logger has emitted the given log""" 71 logs = self.log_handler[level] 72 self.assertTrue(logs, 'Logger hasn\'t emitted "{}"'.format(msg)) 73 if len(logs) == 1: 74 self.assertEqual(msg, logs[0]) 75 else: 76 self.assertIn(msg, logs) 77 logs.remove(msg) 78 79 def assertNoLog(self, level, msg): 80 """Checks that the logger has NOT emitted the given log""" 81 self.assertNotIn(msg, self.log_handler[level]) 82