X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;ds=sidebyside;f=test%2Fpy%2Fmultiplexed_log.py;h=f23d5dec68cd3b8c17e83c09b4820d8d4900fb0f;hb=7c98ca10eae855571b16cddb19d461a981052730;hp=c2a3b89536430fc0c911f83aae44a8783a1caef2;hpb=e8debf394fbba594fcfc267c61f8c6bbca395b06;p=oweals%2Fu-boot.git diff --git a/test/py/multiplexed_log.py b/test/py/multiplexed_log.py index c2a3b89536..f23d5dec68 100644 --- a/test/py/multiplexed_log.py +++ b/test/py/multiplexed_log.py @@ -1,12 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 # Copyright (c) 2015 Stephen Warren # Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved. -# -# SPDX-License-Identifier: GPL-2.0 # Generate an HTML-formatted log file containing multiple streams of data, # each represented in a well-delineated/-structured fashion. import cgi +import datetime import os.path import shutil import subprocess @@ -101,6 +101,8 @@ class RunAndLog(object): self.logfile = logfile self.name = name self.chained_file = chained_file + self.output = None + self.exit_status = None def close(self): """Clean up any resources managed by this object.""" @@ -109,6 +111,9 @@ class RunAndLog(object): def run(self, cmd, cwd=None, ignore_errors=False): """Run a command as a sub-process, and log the results. + The output is available at self.output which can be useful if there is + an exception. + Args: cmd: The command to execute. cwd: The directory to run the command in. Can be None to use the @@ -119,10 +124,10 @@ class RunAndLog(object): raised if such problems occur. Returns: - Nothing. + The output as a string. """ - msg = "+" + " ".join(cmd) + "\n" + msg = '+' + ' '.join(cmd) + '\n' if self.chained_file: self.chained_file.write(msg) self.logfile.write(self, msg) @@ -159,8 +164,14 @@ class RunAndLog(object): self.logfile.write(self, output) if self.chained_file: self.chained_file.write(output) + self.logfile.timestamp() + + # Store the output so it can be accessed if we raise an exception. + self.output = output + self.exit_status = exit_status if exception: raise exception + return output class SectionCtxMgr(object): """A context manager for Python's "with" statement, which allows a certain @@ -168,12 +179,13 @@ class SectionCtxMgr(object): Objects of this type should be created by factory functions in the Logfile class rather than directly.""" - def __init__(self, log, marker): + def __init__(self, log, marker, anchor): """Initialize a new object. Args: log: The Logfile object to log to. marker: The name of the nested log section. + anchor: The anchor value to pass to start_section(). Returns: Nothing. @@ -181,9 +193,10 @@ class SectionCtxMgr(object): self.log = log self.marker = marker + self.anchor = anchor def __enter__(self): - self.log.start_section(self.marker) + self.anchor = self.log.start_section(self.marker, self.anchor) def __exit__(self, extype, value, traceback): self.log.end_section(self.marker) @@ -202,19 +215,83 @@ class Logfile(object): Nothing. """ - self.f = open(fn, "wt") + self.f = open(fn, 'wt') self.last_stream = None self.blocks = [] self.cur_evt = 1 - shutil.copy(mod_dir + "/multiplexed_log.css", os.path.dirname(fn)) - self.f.write("""\ + self.anchor = 0 + self.timestamp_start = self._get_time() + self.timestamp_prev = self.timestamp_start + self.timestamp_blocks = [] + self.seen_warning = False + + shutil.copy(mod_dir + '/multiplexed_log.css', os.path.dirname(fn)) + self.f.write('''\ + + -""") +''') def close(self): """Close the log file. @@ -228,17 +305,17 @@ class Logfile(object): Nothing. """ - self.f.write("""\ + self.f.write('''\ -""") +''') self.f.close() # The set of characters that should be represented as hexadecimal codes in # the log file. - _nonprint = ("%" + "".join(chr(c) for c in range(0, 32) if c not in (9, 10)) + - "".join(chr(c) for c in range(127, 256))) + _nonprint = ('%' + ''.join(chr(c) for c in range(0, 32) if c not in (9, 10)) + + ''.join(chr(c) for c in range(127, 256))) def _escape(self, data): """Render data format suitable for inclusion in an HTML document. @@ -253,8 +330,8 @@ class Logfile(object): An escaped version of the data. """ - data = data.replace(chr(13), "") - data = "".join((c in self._nonprint) and ("%%%02x" % ord(c)) or + data = data.replace(chr(13), '') + data = ''.join((c in self._nonprint) and ('%%%02x' % ord(c)) or c for c in data) data = cgi.escape(data) return data @@ -272,46 +349,63 @@ class Logfile(object): self.cur_evt += 1 if not self.last_stream: return - self.f.write("\n") - self.f.write("
End stream: " + - self.last_stream.name + "
\n") - self.f.write("\n") + self.f.write('\n') + self.f.write('
End stream: ' + + self.last_stream.name + '
\n') + self.f.write('\n') + self.f.write('\n') self.last_stream = None - def _note(self, note_type, msg): + def _note(self, note_type, msg, anchor=None): """Write a note or one-off message to the log file. Args: note_type: The type of note. This must be a value supported by the accompanying multiplexed_log.css. msg: The note/message to log. + anchor: Optional internal link target. Returns: Nothing. """ self._terminate_stream() - self.f.write("
\n
")
+        self.f.write('
\n') + self.f.write('
')
+        if anchor:
+            self.f.write('' % anchor)
         self.f.write(self._escape(msg))
-        self.f.write("\n
\n") + if anchor: + self.f.write('') + self.f.write('\n
\n') + self.f.write('
\n') - def start_section(self, marker): + def start_section(self, marker, anchor=None): """Begin a new nested section in the log file. Args: marker: The name of the section that is starting. + anchor: The value to use for the anchor. If None, a unique value + will be calculated and used Returns: - Nothing. + Name of the HTML anchor emitted before section. """ self._terminate_stream() self.blocks.append(marker) - blk_path = "/".join(self.blocks) - self.f.write("
\n") - self.f.write("
Section: " + blk_path + "
\n") + self.timestamp_blocks.append(self._get_time()) + if not anchor: + self.anchor += 1 + anchor = str(self.anchor) + blk_path = '/'.join(self.blocks) + self.f.write('
\n') + self.f.write('
Section: ' + + blk_path + '
\n') + self.f.write('
\n') + self.timestamp() + + return anchor def end_section(self, marker): """Terminate the current nested section in the log file. @@ -327,16 +421,22 @@ class Logfile(object): """ if (not self.blocks) or (marker != self.blocks[-1]): - raise Exception("Block nesting mismatch: \"%s\" \"%s\"" % - (marker, "/".join(self.blocks))) + raise Exception('Block nesting mismatch: "%s" "%s"' % + (marker, '/'.join(self.blocks))) self._terminate_stream() - blk_path = "/".join(self.blocks) - self.f.write("
End section: " + blk_path + "
\n") - self.f.write("
\n") + timestamp_now = self._get_time() + timestamp_section_start = self.timestamp_blocks.pop() + delta_section = timestamp_now - timestamp_section_start + self._note("timestamp", + "TIME: SINCE-SECTION: " + str(delta_section)) + blk_path = '/'.join(self.blocks) + self.f.write('
' + + 'End section: ' + blk_path + '
\n') + self.f.write('
\n') + self.f.write('
\n') self.blocks.pop() - def section(self, marker): + def section(self, marker, anchor=None): """Create a temporary section in the log file. This function creates a context manager for Python's "with" statement, @@ -349,12 +449,13 @@ class Logfile(object): Args: marker: The name of the nested section. + anchor: The anchor value to pass to start_section(). Returns: A context manager object. """ - return SectionCtxMgr(self, marker) + return SectionCtxMgr(self, marker, anchor) def error(self, msg): """Write an error note to the log file. @@ -378,8 +479,23 @@ class Logfile(object): Nothing. """ + self.seen_warning = True self._note("warning", msg) + def get_and_reset_warning(self): + """Get and reset the log warning flag. + + Args: + None + + Returns: + Whether a warning was seen since the last call. + """ + + ret = self.seen_warning + self.seen_warning = False + return ret + def info(self, msg): """Write an informational note to the log file. @@ -404,41 +520,108 @@ class Logfile(object): self._note("action", msg) - def status_pass(self, msg): + def _get_time(self): + return datetime.datetime.now() + + def timestamp(self): + """Write a timestamp to the log file. + + Args: + None + + Returns: + Nothing. + """ + + timestamp_now = self._get_time() + delta_prev = timestamp_now - self.timestamp_prev + delta_start = timestamp_now - self.timestamp_start + self.timestamp_prev = timestamp_now + + self._note("timestamp", + "TIME: NOW: " + timestamp_now.strftime("%Y/%m/%d %H:%M:%S.%f")) + self._note("timestamp", + "TIME: SINCE-PREV: " + str(delta_prev)) + self._note("timestamp", + "TIME: SINCE-START: " + str(delta_start)) + + def status_pass(self, msg, anchor=None): + """Write a note to the log file describing test(s) which passed. + + Args: + msg: A message describing the passed test(s). + anchor: Optional internal link target. + + Returns: + Nothing. + """ + + self._note("status-pass", msg, anchor) + + def status_warning(self, msg, anchor=None): """Write a note to the log file describing test(s) which passed. Args: - msg: A message describing passed test(s). + msg: A message describing the passed test(s). + anchor: Optional internal link target. Returns: Nothing. """ - self._note("status-pass", msg) + self._note("status-warning", msg, anchor) - def status_skipped(self, msg): + def status_skipped(self, msg, anchor=None): """Write a note to the log file describing skipped test(s). Args: - msg: A message describing passed test(s). + msg: A message describing the skipped test(s). + anchor: Optional internal link target. + + Returns: + Nothing. + """ + + self._note("status-skipped", msg, anchor) + + def status_xfail(self, msg, anchor=None): + """Write a note to the log file describing xfailed test(s). + + Args: + msg: A message describing the xfailed test(s). + anchor: Optional internal link target. + + Returns: + Nothing. + """ + + self._note("status-xfail", msg, anchor) + + def status_xpass(self, msg, anchor=None): + """Write a note to the log file describing xpassed test(s). + + Args: + msg: A message describing the xpassed test(s). + anchor: Optional internal link target. Returns: Nothing. """ - self._note("status-skipped", msg) + self._note("status-xpass", msg, anchor) - def status_fail(self, msg): + def status_fail(self, msg, anchor=None): """Write a note to the log file describing failed test(s). Args: - msg: A message describing passed test(s). + msg: A message describing the failed test(s). + anchor: Optional internal link target. Returns: Nothing. """ - self._note("status-fail", msg) + self._note("status-fail", msg, anchor) def get_stream(self, name, chained_file=None): """Create an object to log a single stream's data into the log file. @@ -495,15 +678,16 @@ class Logfile(object): if stream != self.last_stream: self._terminate_stream() - self.f.write("
\n" % stream.name) - self.f.write("
Stream: " + stream.name + "
\n") - self.f.write("
")
+            self.f.write('
\n') + self.f.write('
Stream: ' + + stream.name + '
\n') + self.f.write('
\n') + self.f.write('
')
         if implicit:
-            self.f.write("")
+            self.f.write('')
         self.f.write(self._escape(data))
         if implicit:
-            self.f.write("")
+            self.f.write('')
         self.last_stream = stream
 
     def flush(self):