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