183d290c5STom Rini# SPDX-License-Identifier: GPL-2.0 2d201506cSStephen Warren# Copyright (c) 2015 Stephen Warren 3d201506cSStephen Warren# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved. 4d201506cSStephen Warren 5d201506cSStephen Warren# Generate an HTML-formatted log file containing multiple streams of data, 6d201506cSStephen Warren# each represented in a well-delineated/-structured fashion. 7d201506cSStephen Warren 8d201506cSStephen Warrenimport cgi 99679d339SStephen Warrenimport datetime 10d201506cSStephen Warrenimport os.path 11d201506cSStephen Warrenimport shutil 12d201506cSStephen Warrenimport subprocess 13d201506cSStephen Warren 14d201506cSStephen Warrenmod_dir = os.path.dirname(os.path.abspath(__file__)) 15d201506cSStephen Warren 16d201506cSStephen Warrenclass LogfileStream(object): 17e8debf39SStephen Warren """A file-like object used to write a single logical stream of data into 18d201506cSStephen Warren a multiplexed log file. Objects of this type should be created by factory 19e8debf39SStephen Warren functions in the Logfile class rather than directly.""" 20d201506cSStephen Warren 21d201506cSStephen Warren def __init__(self, logfile, name, chained_file): 22e8debf39SStephen Warren """Initialize a new object. 23d201506cSStephen Warren 24d201506cSStephen Warren Args: 25d201506cSStephen Warren logfile: The Logfile object to log to. 26d201506cSStephen Warren name: The name of this log stream. 27d201506cSStephen Warren chained_file: The file-like object to which all stream data should be 28d201506cSStephen Warren logged to in addition to logfile. Can be None. 29d201506cSStephen Warren 30d201506cSStephen Warren Returns: 31d201506cSStephen Warren Nothing. 32e8debf39SStephen Warren """ 33d201506cSStephen Warren 34d201506cSStephen Warren self.logfile = logfile 35d201506cSStephen Warren self.name = name 36d201506cSStephen Warren self.chained_file = chained_file 37d201506cSStephen Warren 38d201506cSStephen Warren def close(self): 39e8debf39SStephen Warren """Dummy function so that this class is "file-like". 40d201506cSStephen Warren 41d201506cSStephen Warren Args: 42d201506cSStephen Warren None. 43d201506cSStephen Warren 44d201506cSStephen Warren Returns: 45d201506cSStephen Warren Nothing. 46e8debf39SStephen Warren """ 47d201506cSStephen Warren 48d201506cSStephen Warren pass 49d201506cSStephen Warren 50d201506cSStephen Warren def write(self, data, implicit=False): 51e8debf39SStephen Warren """Write data to the log stream. 52d201506cSStephen Warren 53d201506cSStephen Warren Args: 54d201506cSStephen Warren data: The data to write tot he file. 55d201506cSStephen Warren implicit: Boolean indicating whether data actually appeared in the 56d201506cSStephen Warren stream, or was implicitly generated. A valid use-case is to 57d201506cSStephen Warren repeat a shell prompt at the start of each separate log 58d201506cSStephen Warren section, which makes the log sections more readable in 59d201506cSStephen Warren isolation. 60d201506cSStephen Warren 61d201506cSStephen Warren Returns: 62d201506cSStephen Warren Nothing. 63e8debf39SStephen Warren """ 64d201506cSStephen Warren 65d201506cSStephen Warren self.logfile.write(self, data, implicit) 66d201506cSStephen Warren if self.chained_file: 67d201506cSStephen Warren self.chained_file.write(data) 68d201506cSStephen Warren 69d201506cSStephen Warren def flush(self): 70e8debf39SStephen Warren """Flush the log stream, to ensure correct log interleaving. 71d201506cSStephen Warren 72d201506cSStephen Warren Args: 73d201506cSStephen Warren None. 74d201506cSStephen Warren 75d201506cSStephen Warren Returns: 76d201506cSStephen Warren Nothing. 77e8debf39SStephen Warren """ 78d201506cSStephen Warren 79d201506cSStephen Warren self.logfile.flush() 80d201506cSStephen Warren if self.chained_file: 81d201506cSStephen Warren self.chained_file.flush() 82d201506cSStephen Warren 83d201506cSStephen Warrenclass RunAndLog(object): 84e8debf39SStephen Warren """A utility object used to execute sub-processes and log their output to 85d201506cSStephen Warren a multiplexed log file. Objects of this type should be created by factory 86e8debf39SStephen Warren functions in the Logfile class rather than directly.""" 87d201506cSStephen Warren 88d201506cSStephen Warren def __init__(self, logfile, name, chained_file): 89e8debf39SStephen Warren """Initialize a new object. 90d201506cSStephen Warren 91d201506cSStephen Warren Args: 92d201506cSStephen Warren logfile: The Logfile object to log to. 93d201506cSStephen Warren name: The name of this log stream or sub-process. 94d201506cSStephen Warren chained_file: The file-like object to which all stream data should 95d201506cSStephen Warren be logged to in addition to logfile. Can be None. 96d201506cSStephen Warren 97d201506cSStephen Warren Returns: 98d201506cSStephen Warren Nothing. 99e8debf39SStephen Warren """ 100d201506cSStephen Warren 101d201506cSStephen Warren self.logfile = logfile 102d201506cSStephen Warren self.name = name 103d201506cSStephen Warren self.chained_file = chained_file 10486845bf3SSimon Glass self.output = None 1057f64b187SSimon Glass self.exit_status = None 106d201506cSStephen Warren 107d201506cSStephen Warren def close(self): 108e8debf39SStephen Warren """Clean up any resources managed by this object.""" 109d201506cSStephen Warren pass 110d201506cSStephen Warren 1113f2faf73SStephen Warren def run(self, cmd, cwd=None, ignore_errors=False): 112e8debf39SStephen Warren """Run a command as a sub-process, and log the results. 113d201506cSStephen Warren 11486845bf3SSimon Glass The output is available at self.output which can be useful if there is 11586845bf3SSimon Glass an exception. 11686845bf3SSimon Glass 117d201506cSStephen Warren Args: 118d201506cSStephen Warren cmd: The command to execute. 119d201506cSStephen Warren cwd: The directory to run the command in. Can be None to use the 120d201506cSStephen Warren current directory. 1213f2faf73SStephen Warren ignore_errors: Indicate whether to ignore errors. If True, the 1223f2faf73SStephen Warren function will simply return if the command cannot be executed 1233f2faf73SStephen Warren or exits with an error code, otherwise an exception will be 1243f2faf73SStephen Warren raised if such problems occur. 125d201506cSStephen Warren 126d201506cSStephen Warren Returns: 1273b8d9d97SSimon Glass The output as a string. 128e8debf39SStephen Warren """ 129d201506cSStephen Warren 130a2ec5606SStephen Warren msg = '+' + ' '.join(cmd) + '\n' 131d201506cSStephen Warren if self.chained_file: 132d201506cSStephen Warren self.chained_file.write(msg) 133d201506cSStephen Warren self.logfile.write(self, msg) 134d201506cSStephen Warren 135d201506cSStephen Warren try: 136d201506cSStephen Warren p = subprocess.Popen(cmd, cwd=cwd, 137d201506cSStephen Warren stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 138d201506cSStephen Warren (stdout, stderr) = p.communicate() 139d201506cSStephen Warren output = '' 140d201506cSStephen Warren if stdout: 141d201506cSStephen Warren if stderr: 142d201506cSStephen Warren output += 'stdout:\n' 143d201506cSStephen Warren output += stdout 144d201506cSStephen Warren if stderr: 145d201506cSStephen Warren if stdout: 146d201506cSStephen Warren output += 'stderr:\n' 147d201506cSStephen Warren output += stderr 148d201506cSStephen Warren exit_status = p.returncode 149d201506cSStephen Warren exception = None 150d201506cSStephen Warren except subprocess.CalledProcessError as cpe: 151d201506cSStephen Warren output = cpe.output 152d201506cSStephen Warren exit_status = cpe.returncode 153d201506cSStephen Warren exception = cpe 154d201506cSStephen Warren except Exception as e: 155d201506cSStephen Warren output = '' 156d201506cSStephen Warren exit_status = 0 157d201506cSStephen Warren exception = e 158d201506cSStephen Warren if output and not output.endswith('\n'): 159d201506cSStephen Warren output += '\n' 1603f2faf73SStephen Warren if exit_status and not exception and not ignore_errors: 161d201506cSStephen Warren exception = Exception('Exit code: ' + str(exit_status)) 162d201506cSStephen Warren if exception: 163d201506cSStephen Warren output += str(exception) + '\n' 164d201506cSStephen Warren self.logfile.write(self, output) 165d201506cSStephen Warren if self.chained_file: 166d201506cSStephen Warren self.chained_file.write(output) 1679679d339SStephen Warren self.logfile.timestamp() 16886845bf3SSimon Glass 16986845bf3SSimon Glass # Store the output so it can be accessed if we raise an exception. 17086845bf3SSimon Glass self.output = output 1717f64b187SSimon Glass self.exit_status = exit_status 172d201506cSStephen Warren if exception: 173d201506cSStephen Warren raise exception 1743b8d9d97SSimon Glass return output 175d201506cSStephen Warren 176d201506cSStephen Warrenclass SectionCtxMgr(object): 177e8debf39SStephen Warren """A context manager for Python's "with" statement, which allows a certain 178d201506cSStephen Warren portion of test code to be logged to a separate section of the log file. 179d201506cSStephen Warren Objects of this type should be created by factory functions in the Logfile 180e8debf39SStephen Warren class rather than directly.""" 181d201506cSStephen Warren 18283357fd5SStephen Warren def __init__(self, log, marker, anchor): 183e8debf39SStephen Warren """Initialize a new object. 184d201506cSStephen Warren 185d201506cSStephen Warren Args: 186d201506cSStephen Warren log: The Logfile object to log to. 187d201506cSStephen Warren marker: The name of the nested log section. 18883357fd5SStephen Warren anchor: The anchor value to pass to start_section(). 189d201506cSStephen Warren 190d201506cSStephen Warren Returns: 191d201506cSStephen Warren Nothing. 192e8debf39SStephen Warren """ 193d201506cSStephen Warren 194d201506cSStephen Warren self.log = log 195d201506cSStephen Warren self.marker = marker 19683357fd5SStephen Warren self.anchor = anchor 197d201506cSStephen Warren 198d201506cSStephen Warren def __enter__(self): 19983357fd5SStephen Warren self.anchor = self.log.start_section(self.marker, self.anchor) 200d201506cSStephen Warren 201d201506cSStephen Warren def __exit__(self, extype, value, traceback): 202d201506cSStephen Warren self.log.end_section(self.marker) 203d201506cSStephen Warren 204d201506cSStephen Warrenclass Logfile(object): 205e8debf39SStephen Warren """Generates an HTML-formatted log file containing multiple streams of 206e8debf39SStephen Warren data, each represented in a well-delineated/-structured fashion.""" 207d201506cSStephen Warren 208d201506cSStephen Warren def __init__(self, fn): 209e8debf39SStephen Warren """Initialize a new object. 210d201506cSStephen Warren 211d201506cSStephen Warren Args: 212d201506cSStephen Warren fn: The filename to write to. 213d201506cSStephen Warren 214d201506cSStephen Warren Returns: 215d201506cSStephen Warren Nothing. 216e8debf39SStephen Warren """ 217d201506cSStephen Warren 218a2ec5606SStephen Warren self.f = open(fn, 'wt') 219d201506cSStephen Warren self.last_stream = None 220d201506cSStephen Warren self.blocks = [] 221d201506cSStephen Warren self.cur_evt = 1 22283357fd5SStephen Warren self.anchor = 0 2239679d339SStephen Warren self.timestamp_start = self._get_time() 2249679d339SStephen Warren self.timestamp_prev = self.timestamp_start 2259679d339SStephen Warren self.timestamp_blocks = [] 22632090e50SStephen Warren self.seen_warning = False 22783357fd5SStephen Warren 228a2ec5606SStephen Warren shutil.copy(mod_dir + '/multiplexed_log.css', os.path.dirname(fn)) 229a2ec5606SStephen Warren self.f.write('''\ 230d201506cSStephen Warren<html> 231d201506cSStephen Warren<head> 232d201506cSStephen Warren<link rel="stylesheet" type="text/css" href="multiplexed_log.css"> 23383357fd5SStephen Warren<script src="http://code.jquery.com/jquery.min.js"></script> 23483357fd5SStephen Warren<script> 23583357fd5SStephen Warren$(document).ready(function () { 23683357fd5SStephen Warren // Copy status report HTML to start of log for easy access 23783357fd5SStephen Warren sts = $(".block#status_report")[0].outerHTML; 23883357fd5SStephen Warren $("tt").prepend(sts); 23983357fd5SStephen Warren 24083357fd5SStephen Warren // Add expand/contract buttons to all block headers 24183357fd5SStephen Warren btns = "<span class=\\\"block-expand hidden\\\">[+] </span>" + 24283357fd5SStephen Warren "<span class=\\\"block-contract\\\">[-] </span>"; 24383357fd5SStephen Warren $(".block-header").prepend(btns); 24483357fd5SStephen Warren 24583357fd5SStephen Warren // Pre-contract all blocks which passed, leaving only problem cases 24683357fd5SStephen Warren // expanded, to highlight issues the user should look at. 24783357fd5SStephen Warren // Only top-level blocks (sections) should have any status 24883357fd5SStephen Warren passed_bcs = $(".block-content:has(.status-pass)"); 24983357fd5SStephen Warren // Some blocks might have multiple status entries (e.g. the status 25083357fd5SStephen Warren // report), so take care not to hide blocks with partial success. 25183357fd5SStephen Warren passed_bcs = passed_bcs.not(":has(.status-fail)"); 25283357fd5SStephen Warren passed_bcs = passed_bcs.not(":has(.status-xfail)"); 25383357fd5SStephen Warren passed_bcs = passed_bcs.not(":has(.status-xpass)"); 25483357fd5SStephen Warren passed_bcs = passed_bcs.not(":has(.status-skipped)"); 25532090e50SStephen Warren passed_bcs = passed_bcs.not(":has(.status-warning)"); 25683357fd5SStephen Warren // Hide the passed blocks 25783357fd5SStephen Warren passed_bcs.addClass("hidden"); 25883357fd5SStephen Warren // Flip the expand/contract button hiding for those blocks. 25983357fd5SStephen Warren bhs = passed_bcs.parent().children(".block-header") 26083357fd5SStephen Warren bhs.children(".block-expand").removeClass("hidden"); 26183357fd5SStephen Warren bhs.children(".block-contract").addClass("hidden"); 26283357fd5SStephen Warren 26383357fd5SStephen Warren // Add click handler to block headers. 26483357fd5SStephen Warren // The handler expands/contracts the block. 26583357fd5SStephen Warren $(".block-header").on("click", function (e) { 26683357fd5SStephen Warren var header = $(this); 26783357fd5SStephen Warren var content = header.next(".block-content"); 26883357fd5SStephen Warren var expanded = !content.hasClass("hidden"); 26983357fd5SStephen Warren if (expanded) { 27083357fd5SStephen Warren content.addClass("hidden"); 27183357fd5SStephen Warren header.children(".block-expand").first().removeClass("hidden"); 27283357fd5SStephen Warren header.children(".block-contract").first().addClass("hidden"); 27383357fd5SStephen Warren } else { 27483357fd5SStephen Warren header.children(".block-contract").first().removeClass("hidden"); 27583357fd5SStephen Warren header.children(".block-expand").first().addClass("hidden"); 27683357fd5SStephen Warren content.removeClass("hidden"); 27783357fd5SStephen Warren } 27883357fd5SStephen Warren }); 27983357fd5SStephen Warren 28083357fd5SStephen Warren // When clicking on a link, expand the target block 28183357fd5SStephen Warren $("a").on("click", function (e) { 28283357fd5SStephen Warren var block = $($(this).attr("href")); 28383357fd5SStephen Warren var header = block.children(".block-header"); 28483357fd5SStephen Warren var content = block.children(".block-content").first(); 28583357fd5SStephen Warren header.children(".block-contract").first().removeClass("hidden"); 28683357fd5SStephen Warren header.children(".block-expand").first().addClass("hidden"); 28783357fd5SStephen Warren content.removeClass("hidden"); 28883357fd5SStephen Warren }); 28983357fd5SStephen Warren}); 29083357fd5SStephen Warren</script> 291d201506cSStephen Warren</head> 292d201506cSStephen Warren<body> 293d201506cSStephen Warren<tt> 294a2ec5606SStephen Warren''') 295d201506cSStephen Warren 296d201506cSStephen Warren def close(self): 297e8debf39SStephen Warren """Close the log file. 298d201506cSStephen Warren 299d201506cSStephen Warren After calling this function, no more data may be written to the log. 300d201506cSStephen Warren 301d201506cSStephen Warren Args: 302d201506cSStephen Warren None. 303d201506cSStephen Warren 304d201506cSStephen Warren Returns: 305d201506cSStephen Warren Nothing. 306e8debf39SStephen Warren """ 307d201506cSStephen Warren 308a2ec5606SStephen Warren self.f.write('''\ 309d201506cSStephen Warren</tt> 310d201506cSStephen Warren</body> 311d201506cSStephen Warren</html> 312a2ec5606SStephen Warren''') 313d201506cSStephen Warren self.f.close() 314d201506cSStephen Warren 315d201506cSStephen Warren # The set of characters that should be represented as hexadecimal codes in 316d201506cSStephen Warren # the log file. 317*87b05ee3SSimon Glass _nonprint = {ord('%')} 318*87b05ee3SSimon Glass _nonprint.update({c for c in range(0, 32) if c not in (9, 10)}) 319*87b05ee3SSimon Glass _nonprint.update({c for c in range(127, 256)}) 320d201506cSStephen Warren 321d201506cSStephen Warren def _escape(self, data): 322e8debf39SStephen Warren """Render data format suitable for inclusion in an HTML document. 323d201506cSStephen Warren 324d201506cSStephen Warren This includes HTML-escaping certain characters, and translating 325d201506cSStephen Warren control characters to a hexadecimal representation. 326d201506cSStephen Warren 327d201506cSStephen Warren Args: 328d201506cSStephen Warren data: The raw string data to be escaped. 329d201506cSStephen Warren 330d201506cSStephen Warren Returns: 331d201506cSStephen Warren An escaped version of the data. 332e8debf39SStephen Warren """ 333d201506cSStephen Warren 334a2ec5606SStephen Warren data = data.replace(chr(13), '') 335*87b05ee3SSimon Glass data = ''.join((ord(c) in self._nonprint) and ('%%%02x' % ord(c)) or 336d201506cSStephen Warren c for c in data) 337d201506cSStephen Warren data = cgi.escape(data) 338d201506cSStephen Warren return data 339d201506cSStephen Warren 340d201506cSStephen Warren def _terminate_stream(self): 341e8debf39SStephen Warren """Write HTML to the log file to terminate the current stream's data. 342d201506cSStephen Warren 343d201506cSStephen Warren Args: 344d201506cSStephen Warren None. 345d201506cSStephen Warren 346d201506cSStephen Warren Returns: 347d201506cSStephen Warren Nothing. 348e8debf39SStephen Warren """ 349d201506cSStephen Warren 350d201506cSStephen Warren self.cur_evt += 1 351d201506cSStephen Warren if not self.last_stream: 352d201506cSStephen Warren return 353a2ec5606SStephen Warren self.f.write('</pre>\n') 35483357fd5SStephen Warren self.f.write('<div class="stream-trailer block-trailer">End stream: ' + 355a2ec5606SStephen Warren self.last_stream.name + '</div>\n') 356a2ec5606SStephen Warren self.f.write('</div>\n') 35783357fd5SStephen Warren self.f.write('</div>\n') 358d201506cSStephen Warren self.last_stream = None 359d201506cSStephen Warren 36083357fd5SStephen Warren def _note(self, note_type, msg, anchor=None): 361e8debf39SStephen Warren """Write a note or one-off message to the log file. 362d201506cSStephen Warren 363d201506cSStephen Warren Args: 364d201506cSStephen Warren note_type: The type of note. This must be a value supported by the 365d201506cSStephen Warren accompanying multiplexed_log.css. 366d201506cSStephen Warren msg: The note/message to log. 36783357fd5SStephen Warren anchor: Optional internal link target. 368d201506cSStephen Warren 369d201506cSStephen Warren Returns: 370d201506cSStephen Warren Nothing. 371e8debf39SStephen Warren """ 372d201506cSStephen Warren 373d201506cSStephen Warren self._terminate_stream() 37483357fd5SStephen Warren self.f.write('<div class="' + note_type + '">\n') 37583357fd5SStephen Warren self.f.write('<pre>') 37683357fd5SStephen Warren if anchor: 377117eeb7fSStephen Warren self.f.write('<a href="#%s">' % anchor) 378117eeb7fSStephen Warren self.f.write(self._escape(msg)) 379117eeb7fSStephen Warren if anchor: 380117eeb7fSStephen Warren self.f.write('</a>') 381117eeb7fSStephen Warren self.f.write('\n</pre>\n') 38283357fd5SStephen Warren self.f.write('</div>\n') 383d201506cSStephen Warren 38483357fd5SStephen Warren def start_section(self, marker, anchor=None): 385e8debf39SStephen Warren """Begin a new nested section in the log file. 386d201506cSStephen Warren 387d201506cSStephen Warren Args: 388d201506cSStephen Warren marker: The name of the section that is starting. 38983357fd5SStephen Warren anchor: The value to use for the anchor. If None, a unique value 39083357fd5SStephen Warren will be calculated and used 391d201506cSStephen Warren 392d201506cSStephen Warren Returns: 39383357fd5SStephen Warren Name of the HTML anchor emitted before section. 394e8debf39SStephen Warren """ 395d201506cSStephen Warren 396d201506cSStephen Warren self._terminate_stream() 397d201506cSStephen Warren self.blocks.append(marker) 3989679d339SStephen Warren self.timestamp_blocks.append(self._get_time()) 39983357fd5SStephen Warren if not anchor: 40083357fd5SStephen Warren self.anchor += 1 40183357fd5SStephen Warren anchor = str(self.anchor) 402a2ec5606SStephen Warren blk_path = '/'.join(self.blocks) 40383357fd5SStephen Warren self.f.write('<div class="section block" id="' + anchor + '">\n') 40483357fd5SStephen Warren self.f.write('<div class="section-header block-header">Section: ' + 40583357fd5SStephen Warren blk_path + '</div>\n') 40683357fd5SStephen Warren self.f.write('<div class="section-content block-content">\n') 4079679d339SStephen Warren self.timestamp() 40883357fd5SStephen Warren 40983357fd5SStephen Warren return anchor 410d201506cSStephen Warren 411d201506cSStephen Warren def end_section(self, marker): 412e8debf39SStephen Warren """Terminate the current nested section in the log file. 413d201506cSStephen Warren 414d201506cSStephen Warren This function validates proper nesting of start_section() and 415d201506cSStephen Warren end_section() calls. If a mismatch is found, an exception is raised. 416d201506cSStephen Warren 417d201506cSStephen Warren Args: 418d201506cSStephen Warren marker: The name of the section that is ending. 419d201506cSStephen Warren 420d201506cSStephen Warren Returns: 421d201506cSStephen Warren Nothing. 422e8debf39SStephen Warren """ 423d201506cSStephen Warren 424d201506cSStephen Warren if (not self.blocks) or (marker != self.blocks[-1]): 425a2ec5606SStephen Warren raise Exception('Block nesting mismatch: "%s" "%s"' % 426a2ec5606SStephen Warren (marker, '/'.join(self.blocks))) 427d201506cSStephen Warren self._terminate_stream() 4289679d339SStephen Warren timestamp_now = self._get_time() 4299679d339SStephen Warren timestamp_section_start = self.timestamp_blocks.pop() 4309679d339SStephen Warren delta_section = timestamp_now - timestamp_section_start 4319679d339SStephen Warren self._note("timestamp", 4329679d339SStephen Warren "TIME: SINCE-SECTION: " + str(delta_section)) 433a2ec5606SStephen Warren blk_path = '/'.join(self.blocks) 43483357fd5SStephen Warren self.f.write('<div class="section-trailer block-trailer">' + 43583357fd5SStephen Warren 'End section: ' + blk_path + '</div>\n') 43683357fd5SStephen Warren self.f.write('</div>\n') 437a2ec5606SStephen Warren self.f.write('</div>\n') 438d201506cSStephen Warren self.blocks.pop() 439d201506cSStephen Warren 44083357fd5SStephen Warren def section(self, marker, anchor=None): 441e8debf39SStephen Warren """Create a temporary section in the log file. 442d201506cSStephen Warren 443d201506cSStephen Warren This function creates a context manager for Python's "with" statement, 444d201506cSStephen Warren which allows a certain portion of test code to be logged to a separate 445d201506cSStephen Warren section of the log file. 446d201506cSStephen Warren 447d201506cSStephen Warren Usage: 448d201506cSStephen Warren with log.section("somename"): 449d201506cSStephen Warren some test code 450d201506cSStephen Warren 451d201506cSStephen Warren Args: 452d201506cSStephen Warren marker: The name of the nested section. 45383357fd5SStephen Warren anchor: The anchor value to pass to start_section(). 454d201506cSStephen Warren 455d201506cSStephen Warren Returns: 456d201506cSStephen Warren A context manager object. 457e8debf39SStephen Warren """ 458d201506cSStephen Warren 45983357fd5SStephen Warren return SectionCtxMgr(self, marker, anchor) 460d201506cSStephen Warren 461d201506cSStephen Warren def error(self, msg): 462e8debf39SStephen Warren """Write an error note to the log file. 463d201506cSStephen Warren 464d201506cSStephen Warren Args: 465d201506cSStephen Warren msg: A message describing the error. 466d201506cSStephen Warren 467d201506cSStephen Warren Returns: 468d201506cSStephen Warren Nothing. 469e8debf39SStephen Warren """ 470d201506cSStephen Warren 471d201506cSStephen Warren self._note("error", msg) 472d201506cSStephen Warren 473d201506cSStephen Warren def warning(self, msg): 474e8debf39SStephen Warren """Write an warning note to the log file. 475d201506cSStephen Warren 476d201506cSStephen Warren Args: 477d201506cSStephen Warren msg: A message describing the warning. 478d201506cSStephen Warren 479d201506cSStephen Warren Returns: 480d201506cSStephen Warren Nothing. 481e8debf39SStephen Warren """ 482d201506cSStephen Warren 48332090e50SStephen Warren self.seen_warning = True 484d201506cSStephen Warren self._note("warning", msg) 485d201506cSStephen Warren 48632090e50SStephen Warren def get_and_reset_warning(self): 48732090e50SStephen Warren """Get and reset the log warning flag. 48832090e50SStephen Warren 48932090e50SStephen Warren Args: 49032090e50SStephen Warren None 49132090e50SStephen Warren 49232090e50SStephen Warren Returns: 49332090e50SStephen Warren Whether a warning was seen since the last call. 49432090e50SStephen Warren """ 49532090e50SStephen Warren 49632090e50SStephen Warren ret = self.seen_warning 49732090e50SStephen Warren self.seen_warning = False 49832090e50SStephen Warren return ret 49932090e50SStephen Warren 500d201506cSStephen Warren def info(self, msg): 501e8debf39SStephen Warren """Write an informational note to the log file. 502d201506cSStephen Warren 503d201506cSStephen Warren Args: 504d201506cSStephen Warren msg: An informational message. 505d201506cSStephen Warren 506d201506cSStephen Warren Returns: 507d201506cSStephen Warren Nothing. 508e8debf39SStephen Warren """ 509d201506cSStephen Warren 510d201506cSStephen Warren self._note("info", msg) 511d201506cSStephen Warren 512d201506cSStephen Warren def action(self, msg): 513e8debf39SStephen Warren """Write an action note to the log file. 514d201506cSStephen Warren 515d201506cSStephen Warren Args: 516d201506cSStephen Warren msg: A message describing the action that is being logged. 517d201506cSStephen Warren 518d201506cSStephen Warren Returns: 519d201506cSStephen Warren Nothing. 520e8debf39SStephen Warren """ 521d201506cSStephen Warren 522d201506cSStephen Warren self._note("action", msg) 523d201506cSStephen Warren 5249679d339SStephen Warren def _get_time(self): 5259679d339SStephen Warren return datetime.datetime.now() 5269679d339SStephen Warren 5279679d339SStephen Warren def timestamp(self): 5289679d339SStephen Warren """Write a timestamp to the log file. 5299679d339SStephen Warren 5309679d339SStephen Warren Args: 5319679d339SStephen Warren None 5329679d339SStephen Warren 5339679d339SStephen Warren Returns: 5349679d339SStephen Warren Nothing. 5359679d339SStephen Warren """ 5369679d339SStephen Warren 5379679d339SStephen Warren timestamp_now = self._get_time() 5389679d339SStephen Warren delta_prev = timestamp_now - self.timestamp_prev 5399679d339SStephen Warren delta_start = timestamp_now - self.timestamp_start 5409679d339SStephen Warren self.timestamp_prev = timestamp_now 5419679d339SStephen Warren 5429679d339SStephen Warren self._note("timestamp", 5439679d339SStephen Warren "TIME: NOW: " + timestamp_now.strftime("%Y/%m/%d %H:%M:%S.%f")) 5449679d339SStephen Warren self._note("timestamp", 5459679d339SStephen Warren "TIME: SINCE-PREV: " + str(delta_prev)) 5469679d339SStephen Warren self._note("timestamp", 5479679d339SStephen Warren "TIME: SINCE-START: " + str(delta_start)) 5489679d339SStephen Warren 54983357fd5SStephen Warren def status_pass(self, msg, anchor=None): 550e8debf39SStephen Warren """Write a note to the log file describing test(s) which passed. 551d201506cSStephen Warren 552d201506cSStephen Warren Args: 55378b39cc3SStephen Warren msg: A message describing the passed test(s). 55483357fd5SStephen Warren anchor: Optional internal link target. 555d201506cSStephen Warren 556d201506cSStephen Warren Returns: 557d201506cSStephen Warren Nothing. 558e8debf39SStephen Warren """ 559d201506cSStephen Warren 56083357fd5SStephen Warren self._note("status-pass", msg, anchor) 561d201506cSStephen Warren 56232090e50SStephen Warren def status_warning(self, msg, anchor=None): 56332090e50SStephen Warren """Write a note to the log file describing test(s) which passed. 56432090e50SStephen Warren 56532090e50SStephen Warren Args: 56632090e50SStephen Warren msg: A message describing the passed test(s). 56732090e50SStephen Warren anchor: Optional internal link target. 56832090e50SStephen Warren 56932090e50SStephen Warren Returns: 57032090e50SStephen Warren Nothing. 57132090e50SStephen Warren """ 57232090e50SStephen Warren 57332090e50SStephen Warren self._note("status-warning", msg, anchor) 57432090e50SStephen Warren 57583357fd5SStephen Warren def status_skipped(self, msg, anchor=None): 576e8debf39SStephen Warren """Write a note to the log file describing skipped test(s). 577d201506cSStephen Warren 578d201506cSStephen Warren Args: 57978b39cc3SStephen Warren msg: A message describing the skipped test(s). 58083357fd5SStephen Warren anchor: Optional internal link target. 581d201506cSStephen Warren 582d201506cSStephen Warren Returns: 583d201506cSStephen Warren Nothing. 584e8debf39SStephen Warren """ 585d201506cSStephen Warren 58683357fd5SStephen Warren self._note("status-skipped", msg, anchor) 587d201506cSStephen Warren 58883357fd5SStephen Warren def status_xfail(self, msg, anchor=None): 58978b39cc3SStephen Warren """Write a note to the log file describing xfailed test(s). 59078b39cc3SStephen Warren 59178b39cc3SStephen Warren Args: 59278b39cc3SStephen Warren msg: A message describing the xfailed test(s). 59383357fd5SStephen Warren anchor: Optional internal link target. 59478b39cc3SStephen Warren 59578b39cc3SStephen Warren Returns: 59678b39cc3SStephen Warren Nothing. 59778b39cc3SStephen Warren """ 59878b39cc3SStephen Warren 59983357fd5SStephen Warren self._note("status-xfail", msg, anchor) 60078b39cc3SStephen Warren 60183357fd5SStephen Warren def status_xpass(self, msg, anchor=None): 60278b39cc3SStephen Warren """Write a note to the log file describing xpassed test(s). 60378b39cc3SStephen Warren 60478b39cc3SStephen Warren Args: 60578b39cc3SStephen Warren msg: A message describing the xpassed test(s). 60683357fd5SStephen Warren anchor: Optional internal link target. 60778b39cc3SStephen Warren 60878b39cc3SStephen Warren Returns: 60978b39cc3SStephen Warren Nothing. 61078b39cc3SStephen Warren """ 61178b39cc3SStephen Warren 61283357fd5SStephen Warren self._note("status-xpass", msg, anchor) 61378b39cc3SStephen Warren 61483357fd5SStephen Warren def status_fail(self, msg, anchor=None): 615e8debf39SStephen Warren """Write a note to the log file describing failed test(s). 616d201506cSStephen Warren 617d201506cSStephen Warren Args: 61878b39cc3SStephen Warren msg: A message describing the failed test(s). 61983357fd5SStephen Warren anchor: Optional internal link target. 620d201506cSStephen Warren 621d201506cSStephen Warren Returns: 622d201506cSStephen Warren Nothing. 623e8debf39SStephen Warren """ 624d201506cSStephen Warren 62583357fd5SStephen Warren self._note("status-fail", msg, anchor) 626d201506cSStephen Warren 627d201506cSStephen Warren def get_stream(self, name, chained_file=None): 628e8debf39SStephen Warren """Create an object to log a single stream's data into the log file. 629d201506cSStephen Warren 630d201506cSStephen Warren This creates a "file-like" object that can be written to in order to 631d201506cSStephen Warren write a single stream's data to the log file. The implementation will 632d201506cSStephen Warren handle any required interleaving of data (from multiple streams) in 633d201506cSStephen Warren the log, in a way that makes it obvious which stream each bit of data 634d201506cSStephen Warren came from. 635d201506cSStephen Warren 636d201506cSStephen Warren Args: 637d201506cSStephen Warren name: The name of the stream. 638d201506cSStephen Warren chained_file: The file-like object to which all stream data should 639d201506cSStephen Warren be logged to in addition to this log. Can be None. 640d201506cSStephen Warren 641d201506cSStephen Warren Returns: 642d201506cSStephen Warren A file-like object. 643e8debf39SStephen Warren """ 644d201506cSStephen Warren 645d201506cSStephen Warren return LogfileStream(self, name, chained_file) 646d201506cSStephen Warren 647d201506cSStephen Warren def get_runner(self, name, chained_file=None): 648e8debf39SStephen Warren """Create an object that executes processes and logs their output. 649d201506cSStephen Warren 650d201506cSStephen Warren Args: 651d201506cSStephen Warren name: The name of this sub-process. 652d201506cSStephen Warren chained_file: The file-like object to which all stream data should 653d201506cSStephen Warren be logged to in addition to logfile. Can be None. 654d201506cSStephen Warren 655d201506cSStephen Warren Returns: 656d201506cSStephen Warren A RunAndLog object. 657e8debf39SStephen Warren """ 658d201506cSStephen Warren 659d201506cSStephen Warren return RunAndLog(self, name, chained_file) 660d201506cSStephen Warren 661d201506cSStephen Warren def write(self, stream, data, implicit=False): 662e8debf39SStephen Warren """Write stream data into the log file. 663d201506cSStephen Warren 664d201506cSStephen Warren This function should only be used by instances of LogfileStream or 665d201506cSStephen Warren RunAndLog. 666d201506cSStephen Warren 667d201506cSStephen Warren Args: 668d201506cSStephen Warren stream: The stream whose data is being logged. 669d201506cSStephen Warren data: The data to log. 670d201506cSStephen Warren implicit: Boolean indicating whether data actually appeared in the 671d201506cSStephen Warren stream, or was implicitly generated. A valid use-case is to 672d201506cSStephen Warren repeat a shell prompt at the start of each separate log 673d201506cSStephen Warren section, which makes the log sections more readable in 674d201506cSStephen Warren isolation. 675d201506cSStephen Warren 676d201506cSStephen Warren Returns: 677d201506cSStephen Warren Nothing. 678e8debf39SStephen Warren """ 679d201506cSStephen Warren 680d201506cSStephen Warren if stream != self.last_stream: 681d201506cSStephen Warren self._terminate_stream() 68283357fd5SStephen Warren self.f.write('<div class="stream block">\n') 68383357fd5SStephen Warren self.f.write('<div class="stream-header block-header">Stream: ' + 68483357fd5SStephen Warren stream.name + '</div>\n') 68583357fd5SStephen Warren self.f.write('<div class="stream-content block-content">\n') 686a2ec5606SStephen Warren self.f.write('<pre>') 687d201506cSStephen Warren if implicit: 688a2ec5606SStephen Warren self.f.write('<span class="implicit">') 689d201506cSStephen Warren self.f.write(self._escape(data)) 690d201506cSStephen Warren if implicit: 691a2ec5606SStephen Warren self.f.write('</span>') 692d201506cSStephen Warren self.last_stream = stream 693d201506cSStephen Warren 694d201506cSStephen Warren def flush(self): 695e8debf39SStephen Warren """Flush the log stream, to ensure correct log interleaving. 696d201506cSStephen Warren 697d201506cSStephen Warren Args: 698d201506cSStephen Warren None. 699d201506cSStephen Warren 700d201506cSStephen Warren Returns: 701d201506cSStephen Warren Nothing. 702e8debf39SStephen Warren """ 703d201506cSStephen Warren 704d201506cSStephen Warren self.f.flush() 705