From 07ce662bd212246e20d85de1e4f3d537565449d1 Mon Sep 17 00:00:00 2001 From: Sean Wilson Date: Thu, 3 Aug 2017 11:28:49 -0500 Subject: tests,ext: Add a new testing library proposal The new test library is split into two parts: The framework which resides in ext/, and the gem5 helping components in /tests/gem5. Change-Id: Ib4f3ae8d7eb96a7306335a3e739b7e8041aa99b9 Signed-off-by: Sean Wilson Reviewed-on: https://gem5-review.googlesource.com/4421 Reviewed-by: Giacomo Travaglini Maintainer: Jason Lowe-Power --- ext/testlib/log.py | 256 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 ext/testlib/log.py (limited to 'ext/testlib/log.py') diff --git a/ext/testlib/log.py b/ext/testlib/log.py new file mode 100644 index 000000000..5ba6f5d4f --- /dev/null +++ b/ext/testlib/log.py @@ -0,0 +1,256 @@ +# Copyright (c) 2017 Mark D. Hill and David A. Wood +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer; +# 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; +# neither the name of the copyright holders nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# 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 +# OWNER OR 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. +# +# Authors: Sean Wilson + +''' +This module supplies the global `test_log` object which all testing +results and messages are reported through. +''' +import wrappers + + +class LogLevel(): + Fatal = 0 + Error = 1 + Warn = 2 + Info = 3 + Debug = 4 + Trace = 5 + + +class RecordTypeCounterMetaclass(type): + ''' + Record type metaclass. + + Adds a static integer value in addition to typeinfo so identifiers + are common across processes, networks and module reloads. + ''' + counter = 0 + def __init__(cls, name, bases, dct): + cls.type_id = RecordTypeCounterMetaclass.counter + RecordTypeCounterMetaclass.counter += 1 + + +class Record(object): + ''' + A generic object that is passed to the :class:`Log` and its handlers. + + ..note: Although not statically enforced, all items in the record should be + be pickleable. This enables logging accross multiple processes. + ''' + __metaclass__ = RecordTypeCounterMetaclass + + def __init__(self, **data): + self.data = data + + def __getitem__(self, item): + if item not in self.data: + raise KeyError('%s not in record %s' %\ + (item, self.__class__.__name__)) + return self.data[item] + + def __str__(self): + return str(self.data) + + +class StatusRecord(Record): + def __init__(self, obj, status): + Record.__init__(self, metadata=obj.metadata, status=status) +class ResultRecord(Record): + def __init__(self, obj, result): + Record.__init__(self, metadata=obj.metadata, result=result) +#TODO Refactor this shit... Not ideal. Should just specify attributes. +class TestStatus(StatusRecord): + pass +class SuiteStatus(StatusRecord): + pass +class LibraryStatus(StatusRecord): + pass +class TestResult(ResultRecord): + pass +class SuiteResult(ResultRecord): + pass +class LibraryResult(ResultRecord): + pass +# Test Output Types +class TestStderr(Record): + pass +class TestStdout(Record): + pass +# Message (Raw String) Types +class TestMessage(Record): + pass +class LibraryMessage(Record): + pass + + +class Log(object): + def __init__(self): + self.handlers = [] + self._opened = False # TODO Guards to methods + self._closed = False # TODO Guards to methods + + def finish_init(self): + self._opened = True + + def close(self): + self._closed = True + for handler in self.handlers: + handler.close() + + def log(self, record): + if not self._opened: + self.finish_init() + if self._closed: + raise Exception('The log has been closed' + ' and is no longer available.') + + map(lambda handler:handler.prehandle(), self.handlers) + for handler in self.handlers: + handler.handle(record) + handler.posthandle() + + def add_handler(self, handler): + if self._opened: + raise Exception('Unable to add a handler once the log is open.') + self.handlers.append(handler) + + def close_handler(self, handler): + handler.close() + self.handlers.remove(handler) + + +class Handler(object): + ''' + Empty implementation of the interface available to handlers which + is expected by the :class:`Log`. + ''' + def __init__(self): + pass + + def handle(self, record): + pass + + def close(self): + pass + + def prehandle(self): + pass + + def posthandle(self): + pass + + +class LogWrapper(object): + _result_typemap = { + wrappers.LoadedLibrary.__name__: LibraryResult, + wrappers.LoadedSuite.__name__: SuiteResult, + wrappers.LoadedTest.__name__: TestResult, + } + _status_typemap = { + wrappers.LoadedLibrary.__name__: LibraryStatus, + wrappers.LoadedSuite.__name__: SuiteStatus, + wrappers.LoadedTest.__name__: TestStatus, + } + def __init__(self, log): + self.log_obj = log + + def log(self, *args, **kwargs): + self.log_obj.log(*args, **kwargs) + + # Library Logging Methods + # TODO Replace these methods in a test/create a wrapper? + # That way they still can log like this it's just hidden that they + # capture the current test. + def message(self, message, level=LogLevel.Info, bold=False, **metadata): + self.log_obj.log(LibraryMessage(message=message, level=level, + bold=bold, **metadata)) + + def error(self, message): + self.message(message, LogLevel.Error) + + def warn(self, message): + self.message(message, LogLevel.Warn) + + def info(self, message): + self.message(message, LogLevel.Info) + + def debug(self, message): + self.message(message, LogLevel.Debug) + + def trace(self, message): + self.message(message, LogLevel.Trace) + + # Ongoing Test Logging Methods + def status_update(self, obj, status): + self.log_obj.log( + self._status_typemap[obj.__class__.__name__](obj, status)) + + def result_update(self, obj, result): + self.log_obj.log( + self._result_typemap[obj.__class__.__name__](obj, result)) + + def test_message(self, test, message, level): + self.log_obj.log(TestMessage(message=message, level=level, + test_uid=test.uid, suite_uid=test.parent_suite.uid)) + + # NOTE If performance starts to drag on logging stdout/err + # replace metadata with just test and suite uid tags. + def test_stdout(self, test, suite, buf): + self.log_obj.log(TestStdout(buffer=buf, metadata=test.metadata)) + + def test_stderr(self, test, suite, buf): + self.log_obj.log(TestStderr(buffer=buf, metadata=test.metadata)) + + def close(self): + self.log_obj.close() + +class TestLogWrapper(object): + def __init__(self, log, test, suite): + self.log_obj = log + self.test = test + + def test_message(self, message, level): + self.log_obj.test_message(test=self.test, + message=message, level=level) + + def error(self, message): + self.test_message(message, LogLevel.Error) + + def warn(self, message): + self.test_message(message, LogLevel.Warn) + + def info(self, message): + self.test_message(message, LogLevel.Info) + + def debug(self, message): + self.test_message(message, LogLevel.Debug) + + def trace(self, message): + self.test_message(message, LogLevel.Trace) + +test_log = LogWrapper(Log()) -- cgit v1.2.3