# Perforce Defect Tracking Integration Project # # # TEST_LOGGER.PY -- UNIT TEST FOR THE LOGGER MODULE # # Gareth Rees, Ravenbrook Limited, 2001-12-07 # # # 1. INTRODUCTION # # This module defines unit tests for the logger module. # # It uses the PyUnit unit test framework [PyUnit]. # # The intended readership is project developers and testers. # # This document is not confidential. import os import sys p4dti_path = os.path.join(os.getcwd(), os.pardir, 'code', 'replicator') if p4dti_path not in sys.path: sys.path.append(p4dti_path) import logger import message import re import string import StringIO import tempfile import unittest import whrandom # 2. BASE CLASS # # This has some utilities common to several test cases. class base(unittest.TestCase): error = "Log failure" msg = "Can't write to log." priorities = range(message.EMERG, message.DEBUG+1) # Emulate a file object but raise an error whenever the write() # method is called. def write(self, text): raise self.error, self.msg def flush(self): pass # This method is suitable for passing to set_log_failed_hook; it # records its arguments. def hook(self, err, context): self.called = self.called + 1 self.err = err self.context = context chars = string.letters + string.digits + '_' def random_string(self, minlen, maxlen): length = int(whrandom.uniform(minlen, maxlen)) def random_char(_, self=self): return whrandom.choice(self.chars) return string.join(map(random_char, range(length)), '') log_re = re.compile('\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d UTC (.*)') def check_expected(self, file, expected, max_length = 10000): file.seek(0) i = 0 while 1: line = file.readline() if line == '': break match = self.log_re.match(line) assert match assert i < len(expected) assert match.group(1) == str(expected[i])[0:max_length] i = i + 1 assert i == len(expected) # 3. FILE LOGGER # # Check that the file logger filters correctly by priority. class file(base): def runTest(self): "File logger filters by priority (test_logger.priority)" for i in range(2): for p in self.priorities: if i == 0: file = StringIO.StringIO() else: filename = tempfile.mktemp() file = open(filename, 'w+') expected = [] max_length = whrandom.randint(30,1000) log = logger.file_logger(file, p, max_length) for q in self.priorities: product = self.random_string(5, 15) text = self.random_string(10, 100) id = whrandom.randint(1, 100000) m = message.message(id, text, q, product) log.log(m) if q <= p: expected.append(m) self.check_expected(file, expected, max_length) file.close() if i == 1: os.remove(filename) # 4. FAILURE PROTOCOL # # Check that set_log_failed_hook works and that log failures are caught # and reported. class fail(base): def runTest(self): "Log failure hook (test_logger.fail)" m = message.message(1, "Text", message.EMERG, "Product") # If you don't set the hook you just get the exception. log = logger.file_logger(self) try: log.log(m) except self.error, msg: assert msg == self.msg else: assert 0 # Make a new logger and set the hook. log = logger.file_logger(self) log.set_log_failed_hook(self.hook) # Hook function gets called first time there's a failure. self.called = 0 log.log(m) assert self.called == 1 assert self.err == "%s: %s" % (self.error, self.msg) assert isinstance(self.context, message.message) assert self.context.id == 1018 # But not on subsequent failures. for i in range(3): log.log(m) assert self.called == 1 # 5. MULTIPLE LOGGERS # # # 5.1. Message forwarding # # Check that the multi_logger class forwards messages to its children. class multi(base): def runTest(self): "Multi-logger forwards messages (test_logger.multi)" file1 = StringIO.StringIO() file2 = StringIO.StringIO() log1 = logger.file_logger(file1, message.ERR) log2 = logger.file_logger(file2, message.DEBUG) m1 = message.message(1, "Text", message.EMERG, "Product_foo") m2 = message.message(2, "Text", message.DEBUG, "Product_bar") log3 = logger.multi_logger([log1, log2]) log3.log(m1) log3.log(m2) self.check_expected(file1, [m1]) self.check_expected(file2, [m1, m2]) # 5.2. Failure handling class multi_fail(base): def runTest(self): "Multi-logger failure handling (test_logger.multi_fail)" m = message.message(1, "Text", message.EMERG, "Product") file1 = StringIO.StringIO() log1 = logger.file_logger(file1) log2 = logger.file_logger(self) log3 = logger.multi_logger([log1, log2]) log3.set_log_failed_hook(self.hook) # Failure from log2. self.called = 0 log3.log(m) assert self.called == 1 assert self.err == "%s: %s" % (self.error, self.msg) assert isinstance(self.context, message.message) assert self.context.id == 1018 # Failure from log1. file1.close() log3.log(m) assert self.called == 2 assert (self.err == "exceptions.ValueError: I/O operation on closed file") assert isinstance(self.context, message.message) assert self.context.id == 1018 # 6. SYSTEM LOG # # This doesn't check anything, it just prints some messages to the # system log. class system(base): def runTest(self): "System log (test_logger.system)" if os.name == 'posix': log = logger.sys_logger() elif os.name == 'nt': log = logger.win32_event_logger(self.random_string(5,10)) else: return for p in self.priorities: product = self.random_string(5, 15) text = self.random_string(10, 100) id = whrandom.randint(1, 100000) m = message.message(id, text, p, product) log.log(m) # RUNNING THE TESTS def tests(): suite = unittest.TestSuite() for t in [fail, file, multi, multi_fail]: suite.addTest(t()) return suite if __name__ == "__main__": unittest.main(defaultTest="tests") # A. REFERENCES # # [PyUnit] "PyUnit - a unit testing framework for Python"; Steve # Purcell; . # # # B. DOCUMENT HISTORY # # 2001-12-07 GDR Created. # # # C. COPYRIGHT AND LICENSE # # This file is copyright (c) 2001 Perforce Software, Inc. All # rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in # the documentation and/or other materials provided with the # distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH # DAMAGE. # # # $Id: //info.ravenbrook.com/project/p4dti/version/2.1/test/test_logger.py#2 $