log.py revision 12882
112882Sspwilson2@wisc.edu# Copyright (c) 2017 Mark D. Hill and David A. Wood 212882Sspwilson2@wisc.edu# All rights reserved. 312882Sspwilson2@wisc.edu# 412882Sspwilson2@wisc.edu# Redistribution and use in source and binary forms, with or without 512882Sspwilson2@wisc.edu# modification, are permitted provided that the following conditions are 612882Sspwilson2@wisc.edu# met: redistributions of source code must retain the above copyright 712882Sspwilson2@wisc.edu# notice, this list of conditions and the following disclaimer; 812882Sspwilson2@wisc.edu# redistributions in binary form must reproduce the above copyright 912882Sspwilson2@wisc.edu# notice, this list of conditions and the following disclaimer in the 1012882Sspwilson2@wisc.edu# documentation and/or other materials provided with the distribution; 1112882Sspwilson2@wisc.edu# neither the name of the copyright holders nor the names of its 1212882Sspwilson2@wisc.edu# contributors may be used to endorse or promote products derived from 1312882Sspwilson2@wisc.edu# this software without specific prior written permission. 1412882Sspwilson2@wisc.edu# 1512882Sspwilson2@wisc.edu# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 1612882Sspwilson2@wisc.edu# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 1712882Sspwilson2@wisc.edu# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 1812882Sspwilson2@wisc.edu# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 1912882Sspwilson2@wisc.edu# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 2012882Sspwilson2@wisc.edu# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 2112882Sspwilson2@wisc.edu# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 2212882Sspwilson2@wisc.edu# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 2312882Sspwilson2@wisc.edu# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 2412882Sspwilson2@wisc.edu# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 2512882Sspwilson2@wisc.edu# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 2612882Sspwilson2@wisc.edu# 2712882Sspwilson2@wisc.edu# Authors: Sean Wilson 2812882Sspwilson2@wisc.edu 2912882Sspwilson2@wisc.edu''' 3012882Sspwilson2@wisc.eduThis module supplies the global `test_log` object which all testing 3112882Sspwilson2@wisc.eduresults and messages are reported through. 3212882Sspwilson2@wisc.edu''' 3312882Sspwilson2@wisc.eduimport wrappers 3412882Sspwilson2@wisc.edu 3512882Sspwilson2@wisc.edu 3612882Sspwilson2@wisc.educlass LogLevel(): 3712882Sspwilson2@wisc.edu Fatal = 0 3812882Sspwilson2@wisc.edu Error = 1 3912882Sspwilson2@wisc.edu Warn = 2 4012882Sspwilson2@wisc.edu Info = 3 4112882Sspwilson2@wisc.edu Debug = 4 4212882Sspwilson2@wisc.edu Trace = 5 4312882Sspwilson2@wisc.edu 4412882Sspwilson2@wisc.edu 4512882Sspwilson2@wisc.educlass RecordTypeCounterMetaclass(type): 4612882Sspwilson2@wisc.edu ''' 4712882Sspwilson2@wisc.edu Record type metaclass. 4812882Sspwilson2@wisc.edu 4912882Sspwilson2@wisc.edu Adds a static integer value in addition to typeinfo so identifiers 5012882Sspwilson2@wisc.edu are common across processes, networks and module reloads. 5112882Sspwilson2@wisc.edu ''' 5212882Sspwilson2@wisc.edu counter = 0 5312882Sspwilson2@wisc.edu def __init__(cls, name, bases, dct): 5412882Sspwilson2@wisc.edu cls.type_id = RecordTypeCounterMetaclass.counter 5512882Sspwilson2@wisc.edu RecordTypeCounterMetaclass.counter += 1 5612882Sspwilson2@wisc.edu 5712882Sspwilson2@wisc.edu 5812882Sspwilson2@wisc.educlass Record(object): 5912882Sspwilson2@wisc.edu ''' 6012882Sspwilson2@wisc.edu A generic object that is passed to the :class:`Log` and its handlers. 6112882Sspwilson2@wisc.edu 6212882Sspwilson2@wisc.edu ..note: Although not statically enforced, all items in the record should be 6312882Sspwilson2@wisc.edu be pickleable. This enables logging accross multiple processes. 6412882Sspwilson2@wisc.edu ''' 6512882Sspwilson2@wisc.edu __metaclass__ = RecordTypeCounterMetaclass 6612882Sspwilson2@wisc.edu 6712882Sspwilson2@wisc.edu def __init__(self, **data): 6812882Sspwilson2@wisc.edu self.data = data 6912882Sspwilson2@wisc.edu 7012882Sspwilson2@wisc.edu def __getitem__(self, item): 7112882Sspwilson2@wisc.edu if item not in self.data: 7212882Sspwilson2@wisc.edu raise KeyError('%s not in record %s' %\ 7312882Sspwilson2@wisc.edu (item, self.__class__.__name__)) 7412882Sspwilson2@wisc.edu return self.data[item] 7512882Sspwilson2@wisc.edu 7612882Sspwilson2@wisc.edu def __str__(self): 7712882Sspwilson2@wisc.edu return str(self.data) 7812882Sspwilson2@wisc.edu 7912882Sspwilson2@wisc.edu 8012882Sspwilson2@wisc.educlass StatusRecord(Record): 8112882Sspwilson2@wisc.edu def __init__(self, obj, status): 8212882Sspwilson2@wisc.edu Record.__init__(self, metadata=obj.metadata, status=status) 8312882Sspwilson2@wisc.educlass ResultRecord(Record): 8412882Sspwilson2@wisc.edu def __init__(self, obj, result): 8512882Sspwilson2@wisc.edu Record.__init__(self, metadata=obj.metadata, result=result) 8612882Sspwilson2@wisc.edu#TODO Refactor this shit... Not ideal. Should just specify attributes. 8712882Sspwilson2@wisc.educlass TestStatus(StatusRecord): 8812882Sspwilson2@wisc.edu pass 8912882Sspwilson2@wisc.educlass SuiteStatus(StatusRecord): 9012882Sspwilson2@wisc.edu pass 9112882Sspwilson2@wisc.educlass LibraryStatus(StatusRecord): 9212882Sspwilson2@wisc.edu pass 9312882Sspwilson2@wisc.educlass TestResult(ResultRecord): 9412882Sspwilson2@wisc.edu pass 9512882Sspwilson2@wisc.educlass SuiteResult(ResultRecord): 9612882Sspwilson2@wisc.edu pass 9712882Sspwilson2@wisc.educlass LibraryResult(ResultRecord): 9812882Sspwilson2@wisc.edu pass 9912882Sspwilson2@wisc.edu# Test Output Types 10012882Sspwilson2@wisc.educlass TestStderr(Record): 10112882Sspwilson2@wisc.edu pass 10212882Sspwilson2@wisc.educlass TestStdout(Record): 10312882Sspwilson2@wisc.edu pass 10412882Sspwilson2@wisc.edu# Message (Raw String) Types 10512882Sspwilson2@wisc.educlass TestMessage(Record): 10612882Sspwilson2@wisc.edu pass 10712882Sspwilson2@wisc.educlass LibraryMessage(Record): 10812882Sspwilson2@wisc.edu pass 10912882Sspwilson2@wisc.edu 11012882Sspwilson2@wisc.edu 11112882Sspwilson2@wisc.educlass Log(object): 11212882Sspwilson2@wisc.edu def __init__(self): 11312882Sspwilson2@wisc.edu self.handlers = [] 11412882Sspwilson2@wisc.edu self._opened = False # TODO Guards to methods 11512882Sspwilson2@wisc.edu self._closed = False # TODO Guards to methods 11612882Sspwilson2@wisc.edu 11712882Sspwilson2@wisc.edu def finish_init(self): 11812882Sspwilson2@wisc.edu self._opened = True 11912882Sspwilson2@wisc.edu 12012882Sspwilson2@wisc.edu def close(self): 12112882Sspwilson2@wisc.edu self._closed = True 12212882Sspwilson2@wisc.edu for handler in self.handlers: 12312882Sspwilson2@wisc.edu handler.close() 12412882Sspwilson2@wisc.edu 12512882Sspwilson2@wisc.edu def log(self, record): 12612882Sspwilson2@wisc.edu if not self._opened: 12712882Sspwilson2@wisc.edu self.finish_init() 12812882Sspwilson2@wisc.edu if self._closed: 12912882Sspwilson2@wisc.edu raise Exception('The log has been closed' 13012882Sspwilson2@wisc.edu ' and is no longer available.') 13112882Sspwilson2@wisc.edu 13212882Sspwilson2@wisc.edu map(lambda handler:handler.prehandle(), self.handlers) 13312882Sspwilson2@wisc.edu for handler in self.handlers: 13412882Sspwilson2@wisc.edu handler.handle(record) 13512882Sspwilson2@wisc.edu handler.posthandle() 13612882Sspwilson2@wisc.edu 13712882Sspwilson2@wisc.edu def add_handler(self, handler): 13812882Sspwilson2@wisc.edu if self._opened: 13912882Sspwilson2@wisc.edu raise Exception('Unable to add a handler once the log is open.') 14012882Sspwilson2@wisc.edu self.handlers.append(handler) 14112882Sspwilson2@wisc.edu 14212882Sspwilson2@wisc.edu def close_handler(self, handler): 14312882Sspwilson2@wisc.edu handler.close() 14412882Sspwilson2@wisc.edu self.handlers.remove(handler) 14512882Sspwilson2@wisc.edu 14612882Sspwilson2@wisc.edu 14712882Sspwilson2@wisc.educlass Handler(object): 14812882Sspwilson2@wisc.edu ''' 14912882Sspwilson2@wisc.edu Empty implementation of the interface available to handlers which 15012882Sspwilson2@wisc.edu is expected by the :class:`Log`. 15112882Sspwilson2@wisc.edu ''' 15212882Sspwilson2@wisc.edu def __init__(self): 15312882Sspwilson2@wisc.edu pass 15412882Sspwilson2@wisc.edu 15512882Sspwilson2@wisc.edu def handle(self, record): 15612882Sspwilson2@wisc.edu pass 15712882Sspwilson2@wisc.edu 15812882Sspwilson2@wisc.edu def close(self): 15912882Sspwilson2@wisc.edu pass 16012882Sspwilson2@wisc.edu 16112882Sspwilson2@wisc.edu def prehandle(self): 16212882Sspwilson2@wisc.edu pass 16312882Sspwilson2@wisc.edu 16412882Sspwilson2@wisc.edu def posthandle(self): 16512882Sspwilson2@wisc.edu pass 16612882Sspwilson2@wisc.edu 16712882Sspwilson2@wisc.edu 16812882Sspwilson2@wisc.educlass LogWrapper(object): 16912882Sspwilson2@wisc.edu _result_typemap = { 17012882Sspwilson2@wisc.edu wrappers.LoadedLibrary.__name__: LibraryResult, 17112882Sspwilson2@wisc.edu wrappers.LoadedSuite.__name__: SuiteResult, 17212882Sspwilson2@wisc.edu wrappers.LoadedTest.__name__: TestResult, 17312882Sspwilson2@wisc.edu } 17412882Sspwilson2@wisc.edu _status_typemap = { 17512882Sspwilson2@wisc.edu wrappers.LoadedLibrary.__name__: LibraryStatus, 17612882Sspwilson2@wisc.edu wrappers.LoadedSuite.__name__: SuiteStatus, 17712882Sspwilson2@wisc.edu wrappers.LoadedTest.__name__: TestStatus, 17812882Sspwilson2@wisc.edu } 17912882Sspwilson2@wisc.edu def __init__(self, log): 18012882Sspwilson2@wisc.edu self.log_obj = log 18112882Sspwilson2@wisc.edu 18212882Sspwilson2@wisc.edu def log(self, *args, **kwargs): 18312882Sspwilson2@wisc.edu self.log_obj.log(*args, **kwargs) 18412882Sspwilson2@wisc.edu 18512882Sspwilson2@wisc.edu # Library Logging Methods 18612882Sspwilson2@wisc.edu # TODO Replace these methods in a test/create a wrapper? 18712882Sspwilson2@wisc.edu # That way they still can log like this it's just hidden that they 18812882Sspwilson2@wisc.edu # capture the current test. 18912882Sspwilson2@wisc.edu def message(self, message, level=LogLevel.Info, bold=False, **metadata): 19012882Sspwilson2@wisc.edu self.log_obj.log(LibraryMessage(message=message, level=level, 19112882Sspwilson2@wisc.edu bold=bold, **metadata)) 19212882Sspwilson2@wisc.edu 19312882Sspwilson2@wisc.edu def error(self, message): 19412882Sspwilson2@wisc.edu self.message(message, LogLevel.Error) 19512882Sspwilson2@wisc.edu 19612882Sspwilson2@wisc.edu def warn(self, message): 19712882Sspwilson2@wisc.edu self.message(message, LogLevel.Warn) 19812882Sspwilson2@wisc.edu 19912882Sspwilson2@wisc.edu def info(self, message): 20012882Sspwilson2@wisc.edu self.message(message, LogLevel.Info) 20112882Sspwilson2@wisc.edu 20212882Sspwilson2@wisc.edu def debug(self, message): 20312882Sspwilson2@wisc.edu self.message(message, LogLevel.Debug) 20412882Sspwilson2@wisc.edu 20512882Sspwilson2@wisc.edu def trace(self, message): 20612882Sspwilson2@wisc.edu self.message(message, LogLevel.Trace) 20712882Sspwilson2@wisc.edu 20812882Sspwilson2@wisc.edu # Ongoing Test Logging Methods 20912882Sspwilson2@wisc.edu def status_update(self, obj, status): 21012882Sspwilson2@wisc.edu self.log_obj.log( 21112882Sspwilson2@wisc.edu self._status_typemap[obj.__class__.__name__](obj, status)) 21212882Sspwilson2@wisc.edu 21312882Sspwilson2@wisc.edu def result_update(self, obj, result): 21412882Sspwilson2@wisc.edu self.log_obj.log( 21512882Sspwilson2@wisc.edu self._result_typemap[obj.__class__.__name__](obj, result)) 21612882Sspwilson2@wisc.edu 21712882Sspwilson2@wisc.edu def test_message(self, test, message, level): 21812882Sspwilson2@wisc.edu self.log_obj.log(TestMessage(message=message, level=level, 21912882Sspwilson2@wisc.edu test_uid=test.uid, suite_uid=test.parent_suite.uid)) 22012882Sspwilson2@wisc.edu 22112882Sspwilson2@wisc.edu # NOTE If performance starts to drag on logging stdout/err 22212882Sspwilson2@wisc.edu # replace metadata with just test and suite uid tags. 22312882Sspwilson2@wisc.edu def test_stdout(self, test, suite, buf): 22412882Sspwilson2@wisc.edu self.log_obj.log(TestStdout(buffer=buf, metadata=test.metadata)) 22512882Sspwilson2@wisc.edu 22612882Sspwilson2@wisc.edu def test_stderr(self, test, suite, buf): 22712882Sspwilson2@wisc.edu self.log_obj.log(TestStderr(buffer=buf, metadata=test.metadata)) 22812882Sspwilson2@wisc.edu 22912882Sspwilson2@wisc.edu def close(self): 23012882Sspwilson2@wisc.edu self.log_obj.close() 23112882Sspwilson2@wisc.edu 23212882Sspwilson2@wisc.educlass TestLogWrapper(object): 23312882Sspwilson2@wisc.edu def __init__(self, log, test, suite): 23412882Sspwilson2@wisc.edu self.log_obj = log 23512882Sspwilson2@wisc.edu self.test = test 23612882Sspwilson2@wisc.edu 23712882Sspwilson2@wisc.edu def test_message(self, message, level): 23812882Sspwilson2@wisc.edu self.log_obj.test_message(test=self.test, 23912882Sspwilson2@wisc.edu message=message, level=level) 24012882Sspwilson2@wisc.edu 24112882Sspwilson2@wisc.edu def error(self, message): 24212882Sspwilson2@wisc.edu self.test_message(message, LogLevel.Error) 24312882Sspwilson2@wisc.edu 24412882Sspwilson2@wisc.edu def warn(self, message): 24512882Sspwilson2@wisc.edu self.test_message(message, LogLevel.Warn) 24612882Sspwilson2@wisc.edu 24712882Sspwilson2@wisc.edu def info(self, message): 24812882Sspwilson2@wisc.edu self.test_message(message, LogLevel.Info) 24912882Sspwilson2@wisc.edu 25012882Sspwilson2@wisc.edu def debug(self, message): 25112882Sspwilson2@wisc.edu self.test_message(message, LogLevel.Debug) 25212882Sspwilson2@wisc.edu 25312882Sspwilson2@wisc.edu def trace(self, message): 25412882Sspwilson2@wisc.edu self.test_message(message, LogLevel.Trace) 25512882Sspwilson2@wisc.edu 25612882Sspwilson2@wisc.edutest_log = LogWrapper(Log()) 257