1d201506cSStephen Warren# Copyright (c) 2015 Stephen Warren 2d201506cSStephen Warren# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved. 3d201506cSStephen Warren# 4d201506cSStephen Warren# SPDX-License-Identifier: GPL-2.0 5d201506cSStephen Warren 6d201506cSStephen Warren# Generate an HTML-formatted log file containing multiple streams of data, 7d201506cSStephen Warren# each represented in a well-delineated/-structured fashion. 8d201506cSStephen Warren 9d201506cSStephen Warrenimport cgi 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 104d201506cSStephen Warren 105d201506cSStephen Warren def close(self): 106e8debf39SStephen Warren """Clean up any resources managed by this object.""" 107d201506cSStephen Warren pass 108d201506cSStephen Warren 1093f2faf73SStephen Warren def run(self, cmd, cwd=None, ignore_errors=False): 110e8debf39SStephen Warren """Run a command as a sub-process, and log the results. 111d201506cSStephen Warren 112d201506cSStephen Warren Args: 113d201506cSStephen Warren cmd: The command to execute. 114d201506cSStephen Warren cwd: The directory to run the command in. Can be None to use the 115d201506cSStephen Warren current directory. 1163f2faf73SStephen Warren ignore_errors: Indicate whether to ignore errors. If True, the 1173f2faf73SStephen Warren function will simply return if the command cannot be executed 1183f2faf73SStephen Warren or exits with an error code, otherwise an exception will be 1193f2faf73SStephen Warren raised if such problems occur. 120d201506cSStephen Warren 121d201506cSStephen Warren Returns: 122d201506cSStephen Warren Nothing. 123e8debf39SStephen Warren """ 124d201506cSStephen Warren 125a2ec5606SStephen Warren msg = '+' + ' '.join(cmd) + '\n' 126d201506cSStephen Warren if self.chained_file: 127d201506cSStephen Warren self.chained_file.write(msg) 128d201506cSStephen Warren self.logfile.write(self, msg) 129d201506cSStephen Warren 130d201506cSStephen Warren try: 131d201506cSStephen Warren p = subprocess.Popen(cmd, cwd=cwd, 132d201506cSStephen Warren stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 133d201506cSStephen Warren (stdout, stderr) = p.communicate() 134d201506cSStephen Warren output = '' 135d201506cSStephen Warren if stdout: 136d201506cSStephen Warren if stderr: 137d201506cSStephen Warren output += 'stdout:\n' 138d201506cSStephen Warren output += stdout 139d201506cSStephen Warren if stderr: 140d201506cSStephen Warren if stdout: 141d201506cSStephen Warren output += 'stderr:\n' 142d201506cSStephen Warren output += stderr 143d201506cSStephen Warren exit_status = p.returncode 144d201506cSStephen Warren exception = None 145d201506cSStephen Warren except subprocess.CalledProcessError as cpe: 146d201506cSStephen Warren output = cpe.output 147d201506cSStephen Warren exit_status = cpe.returncode 148d201506cSStephen Warren exception = cpe 149d201506cSStephen Warren except Exception as e: 150d201506cSStephen Warren output = '' 151d201506cSStephen Warren exit_status = 0 152d201506cSStephen Warren exception = e 153d201506cSStephen Warren if output and not output.endswith('\n'): 154d201506cSStephen Warren output += '\n' 1553f2faf73SStephen Warren if exit_status and not exception and not ignore_errors: 156d201506cSStephen Warren exception = Exception('Exit code: ' + str(exit_status)) 157d201506cSStephen Warren if exception: 158d201506cSStephen Warren output += str(exception) + '\n' 159d201506cSStephen Warren self.logfile.write(self, output) 160d201506cSStephen Warren if self.chained_file: 161d201506cSStephen Warren self.chained_file.write(output) 162d201506cSStephen Warren if exception: 163d201506cSStephen Warren raise exception 164d201506cSStephen Warren 165d201506cSStephen Warrenclass SectionCtxMgr(object): 166e8debf39SStephen Warren """A context manager for Python's "with" statement, which allows a certain 167d201506cSStephen Warren portion of test code to be logged to a separate section of the log file. 168d201506cSStephen Warren Objects of this type should be created by factory functions in the Logfile 169e8debf39SStephen Warren class rather than directly.""" 170d201506cSStephen Warren 171*83357fd5SStephen Warren def __init__(self, log, marker, anchor): 172e8debf39SStephen Warren """Initialize a new object. 173d201506cSStephen Warren 174d201506cSStephen Warren Args: 175d201506cSStephen Warren log: The Logfile object to log to. 176d201506cSStephen Warren marker: The name of the nested log section. 177*83357fd5SStephen Warren anchor: The anchor value to pass to start_section(). 178d201506cSStephen Warren 179d201506cSStephen Warren Returns: 180d201506cSStephen Warren Nothing. 181e8debf39SStephen Warren """ 182d201506cSStephen Warren 183d201506cSStephen Warren self.log = log 184d201506cSStephen Warren self.marker = marker 185*83357fd5SStephen Warren self.anchor = anchor 186d201506cSStephen Warren 187d201506cSStephen Warren def __enter__(self): 188*83357fd5SStephen Warren self.anchor = self.log.start_section(self.marker, self.anchor) 189d201506cSStephen Warren 190d201506cSStephen Warren def __exit__(self, extype, value, traceback): 191d201506cSStephen Warren self.log.end_section(self.marker) 192d201506cSStephen Warren 193d201506cSStephen Warrenclass Logfile(object): 194e8debf39SStephen Warren """Generates an HTML-formatted log file containing multiple streams of 195e8debf39SStephen Warren data, each represented in a well-delineated/-structured fashion.""" 196d201506cSStephen Warren 197d201506cSStephen Warren def __init__(self, fn): 198e8debf39SStephen Warren """Initialize a new object. 199d201506cSStephen Warren 200d201506cSStephen Warren Args: 201d201506cSStephen Warren fn: The filename to write to. 202d201506cSStephen Warren 203d201506cSStephen Warren Returns: 204d201506cSStephen Warren Nothing. 205e8debf39SStephen Warren """ 206d201506cSStephen Warren 207a2ec5606SStephen Warren self.f = open(fn, 'wt') 208d201506cSStephen Warren self.last_stream = None 209d201506cSStephen Warren self.blocks = [] 210d201506cSStephen Warren self.cur_evt = 1 211*83357fd5SStephen Warren self.anchor = 0 212*83357fd5SStephen Warren 213a2ec5606SStephen Warren shutil.copy(mod_dir + '/multiplexed_log.css', os.path.dirname(fn)) 214a2ec5606SStephen Warren self.f.write('''\ 215d201506cSStephen Warren<html> 216d201506cSStephen Warren<head> 217d201506cSStephen Warren<link rel="stylesheet" type="text/css" href="multiplexed_log.css"> 218*83357fd5SStephen Warren<script src="http://code.jquery.com/jquery.min.js"></script> 219*83357fd5SStephen Warren<script> 220*83357fd5SStephen Warren$(document).ready(function () { 221*83357fd5SStephen Warren // Copy status report HTML to start of log for easy access 222*83357fd5SStephen Warren sts = $(".block#status_report")[0].outerHTML; 223*83357fd5SStephen Warren $("tt").prepend(sts); 224*83357fd5SStephen Warren 225*83357fd5SStephen Warren // Add expand/contract buttons to all block headers 226*83357fd5SStephen Warren btns = "<span class=\\\"block-expand hidden\\\">[+] </span>" + 227*83357fd5SStephen Warren "<span class=\\\"block-contract\\\">[-] </span>"; 228*83357fd5SStephen Warren $(".block-header").prepend(btns); 229*83357fd5SStephen Warren 230*83357fd5SStephen Warren // Pre-contract all blocks which passed, leaving only problem cases 231*83357fd5SStephen Warren // expanded, to highlight issues the user should look at. 232*83357fd5SStephen Warren // Only top-level blocks (sections) should have any status 233*83357fd5SStephen Warren passed_bcs = $(".block-content:has(.status-pass)"); 234*83357fd5SStephen Warren // Some blocks might have multiple status entries (e.g. the status 235*83357fd5SStephen Warren // report), so take care not to hide blocks with partial success. 236*83357fd5SStephen Warren passed_bcs = passed_bcs.not(":has(.status-fail)"); 237*83357fd5SStephen Warren passed_bcs = passed_bcs.not(":has(.status-xfail)"); 238*83357fd5SStephen Warren passed_bcs = passed_bcs.not(":has(.status-xpass)"); 239*83357fd5SStephen Warren passed_bcs = passed_bcs.not(":has(.status-skipped)"); 240*83357fd5SStephen Warren // Hide the passed blocks 241*83357fd5SStephen Warren passed_bcs.addClass("hidden"); 242*83357fd5SStephen Warren // Flip the expand/contract button hiding for those blocks. 243*83357fd5SStephen Warren bhs = passed_bcs.parent().children(".block-header") 244*83357fd5SStephen Warren bhs.children(".block-expand").removeClass("hidden"); 245*83357fd5SStephen Warren bhs.children(".block-contract").addClass("hidden"); 246*83357fd5SStephen Warren 247*83357fd5SStephen Warren // Add click handler to block headers. 248*83357fd5SStephen Warren // The handler expands/contracts the block. 249*83357fd5SStephen Warren $(".block-header").on("click", function (e) { 250*83357fd5SStephen Warren var header = $(this); 251*83357fd5SStephen Warren var content = header.next(".block-content"); 252*83357fd5SStephen Warren var expanded = !content.hasClass("hidden"); 253*83357fd5SStephen Warren if (expanded) { 254*83357fd5SStephen Warren content.addClass("hidden"); 255*83357fd5SStephen Warren header.children(".block-expand").first().removeClass("hidden"); 256*83357fd5SStephen Warren header.children(".block-contract").first().addClass("hidden"); 257*83357fd5SStephen Warren } else { 258*83357fd5SStephen Warren header.children(".block-contract").first().removeClass("hidden"); 259*83357fd5SStephen Warren header.children(".block-expand").first().addClass("hidden"); 260*83357fd5SStephen Warren content.removeClass("hidden"); 261*83357fd5SStephen Warren } 262*83357fd5SStephen Warren }); 263*83357fd5SStephen Warren 264*83357fd5SStephen Warren // When clicking on a link, expand the target block 265*83357fd5SStephen Warren $("a").on("click", function (e) { 266*83357fd5SStephen Warren var block = $($(this).attr("href")); 267*83357fd5SStephen Warren var header = block.children(".block-header"); 268*83357fd5SStephen Warren var content = block.children(".block-content").first(); 269*83357fd5SStephen Warren header.children(".block-contract").first().removeClass("hidden"); 270*83357fd5SStephen Warren header.children(".block-expand").first().addClass("hidden"); 271*83357fd5SStephen Warren content.removeClass("hidden"); 272*83357fd5SStephen Warren }); 273*83357fd5SStephen Warren}); 274*83357fd5SStephen Warren</script> 275d201506cSStephen Warren</head> 276d201506cSStephen Warren<body> 277d201506cSStephen Warren<tt> 278a2ec5606SStephen Warren''') 279d201506cSStephen Warren 280d201506cSStephen Warren def close(self): 281e8debf39SStephen Warren """Close the log file. 282d201506cSStephen Warren 283d201506cSStephen Warren After calling this function, no more data may be written to the log. 284d201506cSStephen Warren 285d201506cSStephen Warren Args: 286d201506cSStephen Warren None. 287d201506cSStephen Warren 288d201506cSStephen Warren Returns: 289d201506cSStephen Warren Nothing. 290e8debf39SStephen Warren """ 291d201506cSStephen Warren 292a2ec5606SStephen Warren self.f.write('''\ 293d201506cSStephen Warren</tt> 294d201506cSStephen Warren</body> 295d201506cSStephen Warren</html> 296a2ec5606SStephen Warren''') 297d201506cSStephen Warren self.f.close() 298d201506cSStephen Warren 299d201506cSStephen Warren # The set of characters that should be represented as hexadecimal codes in 300d201506cSStephen Warren # the log file. 301a2ec5606SStephen Warren _nonprint = ('%' + ''.join(chr(c) for c in range(0, 32) if c not in (9, 10)) + 302a2ec5606SStephen Warren ''.join(chr(c) for c in range(127, 256))) 303d201506cSStephen Warren 304d201506cSStephen Warren def _escape(self, data): 305e8debf39SStephen Warren """Render data format suitable for inclusion in an HTML document. 306d201506cSStephen Warren 307d201506cSStephen Warren This includes HTML-escaping certain characters, and translating 308d201506cSStephen Warren control characters to a hexadecimal representation. 309d201506cSStephen Warren 310d201506cSStephen Warren Args: 311d201506cSStephen Warren data: The raw string data to be escaped. 312d201506cSStephen Warren 313d201506cSStephen Warren Returns: 314d201506cSStephen Warren An escaped version of the data. 315e8debf39SStephen Warren """ 316d201506cSStephen Warren 317a2ec5606SStephen Warren data = data.replace(chr(13), '') 318a2ec5606SStephen Warren data = ''.join((c in self._nonprint) and ('%%%02x' % ord(c)) or 319d201506cSStephen Warren c for c in data) 320d201506cSStephen Warren data = cgi.escape(data) 321d201506cSStephen Warren return data 322d201506cSStephen Warren 323d201506cSStephen Warren def _terminate_stream(self): 324e8debf39SStephen Warren """Write HTML to the log file to terminate the current stream's data. 325d201506cSStephen Warren 326d201506cSStephen Warren Args: 327d201506cSStephen Warren None. 328d201506cSStephen Warren 329d201506cSStephen Warren Returns: 330d201506cSStephen Warren Nothing. 331e8debf39SStephen Warren """ 332d201506cSStephen Warren 333d201506cSStephen Warren self.cur_evt += 1 334d201506cSStephen Warren if not self.last_stream: 335d201506cSStephen Warren return 336a2ec5606SStephen Warren self.f.write('</pre>\n') 337*83357fd5SStephen Warren self.f.write('<div class="stream-trailer block-trailer">End stream: ' + 338a2ec5606SStephen Warren self.last_stream.name + '</div>\n') 339a2ec5606SStephen Warren self.f.write('</div>\n') 340*83357fd5SStephen Warren self.f.write('</div>\n') 341d201506cSStephen Warren self.last_stream = None 342d201506cSStephen Warren 343*83357fd5SStephen Warren def _note(self, note_type, msg, anchor=None): 344e8debf39SStephen Warren """Write a note or one-off message to the log file. 345d201506cSStephen Warren 346d201506cSStephen Warren Args: 347d201506cSStephen Warren note_type: The type of note. This must be a value supported by the 348d201506cSStephen Warren accompanying multiplexed_log.css. 349d201506cSStephen Warren msg: The note/message to log. 350*83357fd5SStephen Warren anchor: Optional internal link target. 351d201506cSStephen Warren 352d201506cSStephen Warren Returns: 353d201506cSStephen Warren Nothing. 354e8debf39SStephen Warren """ 355d201506cSStephen Warren 356d201506cSStephen Warren self._terminate_stream() 357*83357fd5SStephen Warren self.f.write('<div class="' + note_type + '">\n') 358*83357fd5SStephen Warren if anchor: 359*83357fd5SStephen Warren self.f.write('<a href="#%s">\n' % anchor) 360*83357fd5SStephen Warren self.f.write('<pre>') 361d201506cSStephen Warren self.f.write(self._escape(msg)) 362*83357fd5SStephen Warren self.f.write('\n</pre>\n') 363*83357fd5SStephen Warren if anchor: 364*83357fd5SStephen Warren self.f.write('</a>\n') 365*83357fd5SStephen Warren self.f.write('</div>\n') 366d201506cSStephen Warren 367*83357fd5SStephen Warren def start_section(self, marker, anchor=None): 368e8debf39SStephen Warren """Begin a new nested section in the log file. 369d201506cSStephen Warren 370d201506cSStephen Warren Args: 371d201506cSStephen Warren marker: The name of the section that is starting. 372*83357fd5SStephen Warren anchor: The value to use for the anchor. If None, a unique value 373*83357fd5SStephen Warren will be calculated and used 374d201506cSStephen Warren 375d201506cSStephen Warren Returns: 376*83357fd5SStephen Warren Name of the HTML anchor emitted before section. 377e8debf39SStephen Warren """ 378d201506cSStephen Warren 379d201506cSStephen Warren self._terminate_stream() 380d201506cSStephen Warren self.blocks.append(marker) 381*83357fd5SStephen Warren if not anchor: 382*83357fd5SStephen Warren self.anchor += 1 383*83357fd5SStephen Warren anchor = str(self.anchor) 384a2ec5606SStephen Warren blk_path = '/'.join(self.blocks) 385*83357fd5SStephen Warren self.f.write('<div class="section block" id="' + anchor + '">\n') 386*83357fd5SStephen Warren self.f.write('<div class="section-header block-header">Section: ' + 387*83357fd5SStephen Warren blk_path + '</div>\n') 388*83357fd5SStephen Warren self.f.write('<div class="section-content block-content">\n') 389*83357fd5SStephen Warren 390*83357fd5SStephen Warren return anchor 391d201506cSStephen Warren 392d201506cSStephen Warren def end_section(self, marker): 393e8debf39SStephen Warren """Terminate the current nested section in the log file. 394d201506cSStephen Warren 395d201506cSStephen Warren This function validates proper nesting of start_section() and 396d201506cSStephen Warren end_section() calls. If a mismatch is found, an exception is raised. 397d201506cSStephen Warren 398d201506cSStephen Warren Args: 399d201506cSStephen Warren marker: The name of the section that is ending. 400d201506cSStephen Warren 401d201506cSStephen Warren Returns: 402d201506cSStephen Warren Nothing. 403e8debf39SStephen Warren """ 404d201506cSStephen Warren 405d201506cSStephen Warren if (not self.blocks) or (marker != self.blocks[-1]): 406a2ec5606SStephen Warren raise Exception('Block nesting mismatch: "%s" "%s"' % 407a2ec5606SStephen Warren (marker, '/'.join(self.blocks))) 408d201506cSStephen Warren self._terminate_stream() 409a2ec5606SStephen Warren blk_path = '/'.join(self.blocks) 410*83357fd5SStephen Warren self.f.write('<div class="section-trailer block-trailer">' + 411*83357fd5SStephen Warren 'End section: ' + blk_path + '</div>\n') 412*83357fd5SStephen Warren self.f.write('</div>\n') 413a2ec5606SStephen Warren self.f.write('</div>\n') 414d201506cSStephen Warren self.blocks.pop() 415d201506cSStephen Warren 416*83357fd5SStephen Warren def section(self, marker, anchor=None): 417e8debf39SStephen Warren """Create a temporary section in the log file. 418d201506cSStephen Warren 419d201506cSStephen Warren This function creates a context manager for Python's "with" statement, 420d201506cSStephen Warren which allows a certain portion of test code to be logged to a separate 421d201506cSStephen Warren section of the log file. 422d201506cSStephen Warren 423d201506cSStephen Warren Usage: 424d201506cSStephen Warren with log.section("somename"): 425d201506cSStephen Warren some test code 426d201506cSStephen Warren 427d201506cSStephen Warren Args: 428d201506cSStephen Warren marker: The name of the nested section. 429*83357fd5SStephen Warren anchor: The anchor value to pass to start_section(). 430d201506cSStephen Warren 431d201506cSStephen Warren Returns: 432d201506cSStephen Warren A context manager object. 433e8debf39SStephen Warren """ 434d201506cSStephen Warren 435*83357fd5SStephen Warren return SectionCtxMgr(self, marker, anchor) 436d201506cSStephen Warren 437d201506cSStephen Warren def error(self, msg): 438e8debf39SStephen Warren """Write an error note to the log file. 439d201506cSStephen Warren 440d201506cSStephen Warren Args: 441d201506cSStephen Warren msg: A message describing the error. 442d201506cSStephen Warren 443d201506cSStephen Warren Returns: 444d201506cSStephen Warren Nothing. 445e8debf39SStephen Warren """ 446d201506cSStephen Warren 447d201506cSStephen Warren self._note("error", msg) 448d201506cSStephen Warren 449d201506cSStephen Warren def warning(self, msg): 450e8debf39SStephen Warren """Write an warning note to the log file. 451d201506cSStephen Warren 452d201506cSStephen Warren Args: 453d201506cSStephen Warren msg: A message describing the warning. 454d201506cSStephen Warren 455d201506cSStephen Warren Returns: 456d201506cSStephen Warren Nothing. 457e8debf39SStephen Warren """ 458d201506cSStephen Warren 459d201506cSStephen Warren self._note("warning", msg) 460d201506cSStephen Warren 461d201506cSStephen Warren def info(self, msg): 462e8debf39SStephen Warren """Write an informational note to the log file. 463d201506cSStephen Warren 464d201506cSStephen Warren Args: 465d201506cSStephen Warren msg: An informational message. 466d201506cSStephen Warren 467d201506cSStephen Warren Returns: 468d201506cSStephen Warren Nothing. 469e8debf39SStephen Warren """ 470d201506cSStephen Warren 471d201506cSStephen Warren self._note("info", msg) 472d201506cSStephen Warren 473d201506cSStephen Warren def action(self, msg): 474e8debf39SStephen Warren """Write an action note to the log file. 475d201506cSStephen Warren 476d201506cSStephen Warren Args: 477d201506cSStephen Warren msg: A message describing the action that is being logged. 478d201506cSStephen Warren 479d201506cSStephen Warren Returns: 480d201506cSStephen Warren Nothing. 481e8debf39SStephen Warren """ 482d201506cSStephen Warren 483d201506cSStephen Warren self._note("action", msg) 484d201506cSStephen Warren 485*83357fd5SStephen Warren def status_pass(self, msg, anchor=None): 486e8debf39SStephen Warren """Write a note to the log file describing test(s) which passed. 487d201506cSStephen Warren 488d201506cSStephen Warren Args: 48978b39cc3SStephen Warren msg: A message describing the passed test(s). 490*83357fd5SStephen Warren anchor: Optional internal link target. 491d201506cSStephen Warren 492d201506cSStephen Warren Returns: 493d201506cSStephen Warren Nothing. 494e8debf39SStephen Warren """ 495d201506cSStephen Warren 496*83357fd5SStephen Warren self._note("status-pass", msg, anchor) 497d201506cSStephen Warren 498*83357fd5SStephen Warren def status_skipped(self, msg, anchor=None): 499e8debf39SStephen Warren """Write a note to the log file describing skipped test(s). 500d201506cSStephen Warren 501d201506cSStephen Warren Args: 50278b39cc3SStephen Warren msg: A message describing the skipped test(s). 503*83357fd5SStephen Warren anchor: Optional internal link target. 504d201506cSStephen Warren 505d201506cSStephen Warren Returns: 506d201506cSStephen Warren Nothing. 507e8debf39SStephen Warren """ 508d201506cSStephen Warren 509*83357fd5SStephen Warren self._note("status-skipped", msg, anchor) 510d201506cSStephen Warren 511*83357fd5SStephen Warren def status_xfail(self, msg, anchor=None): 51278b39cc3SStephen Warren """Write a note to the log file describing xfailed test(s). 51378b39cc3SStephen Warren 51478b39cc3SStephen Warren Args: 51578b39cc3SStephen Warren msg: A message describing the xfailed test(s). 516*83357fd5SStephen Warren anchor: Optional internal link target. 51778b39cc3SStephen Warren 51878b39cc3SStephen Warren Returns: 51978b39cc3SStephen Warren Nothing. 52078b39cc3SStephen Warren """ 52178b39cc3SStephen Warren 522*83357fd5SStephen Warren self._note("status-xfail", msg, anchor) 52378b39cc3SStephen Warren 524*83357fd5SStephen Warren def status_xpass(self, msg, anchor=None): 52578b39cc3SStephen Warren """Write a note to the log file describing xpassed test(s). 52678b39cc3SStephen Warren 52778b39cc3SStephen Warren Args: 52878b39cc3SStephen Warren msg: A message describing the xpassed test(s). 529*83357fd5SStephen Warren anchor: Optional internal link target. 53078b39cc3SStephen Warren 53178b39cc3SStephen Warren Returns: 53278b39cc3SStephen Warren Nothing. 53378b39cc3SStephen Warren """ 53478b39cc3SStephen Warren 535*83357fd5SStephen Warren self._note("status-xpass", msg, anchor) 53678b39cc3SStephen Warren 537*83357fd5SStephen Warren def status_fail(self, msg, anchor=None): 538e8debf39SStephen Warren """Write a note to the log file describing failed test(s). 539d201506cSStephen Warren 540d201506cSStephen Warren Args: 54178b39cc3SStephen Warren msg: A message describing the failed test(s). 542*83357fd5SStephen Warren anchor: Optional internal link target. 543d201506cSStephen Warren 544d201506cSStephen Warren Returns: 545d201506cSStephen Warren Nothing. 546e8debf39SStephen Warren """ 547d201506cSStephen Warren 548*83357fd5SStephen Warren self._note("status-fail", msg, anchor) 549d201506cSStephen Warren 550d201506cSStephen Warren def get_stream(self, name, chained_file=None): 551e8debf39SStephen Warren """Create an object to log a single stream's data into the log file. 552d201506cSStephen Warren 553d201506cSStephen Warren This creates a "file-like" object that can be written to in order to 554d201506cSStephen Warren write a single stream's data to the log file. The implementation will 555d201506cSStephen Warren handle any required interleaving of data (from multiple streams) in 556d201506cSStephen Warren the log, in a way that makes it obvious which stream each bit of data 557d201506cSStephen Warren came from. 558d201506cSStephen Warren 559d201506cSStephen Warren Args: 560d201506cSStephen Warren name: The name of the stream. 561d201506cSStephen Warren chained_file: The file-like object to which all stream data should 562d201506cSStephen Warren be logged to in addition to this log. Can be None. 563d201506cSStephen Warren 564d201506cSStephen Warren Returns: 565d201506cSStephen Warren A file-like object. 566e8debf39SStephen Warren """ 567d201506cSStephen Warren 568d201506cSStephen Warren return LogfileStream(self, name, chained_file) 569d201506cSStephen Warren 570d201506cSStephen Warren def get_runner(self, name, chained_file=None): 571e8debf39SStephen Warren """Create an object that executes processes and logs their output. 572d201506cSStephen Warren 573d201506cSStephen Warren Args: 574d201506cSStephen Warren name: The name of this sub-process. 575d201506cSStephen Warren chained_file: The file-like object to which all stream data should 576d201506cSStephen Warren be logged to in addition to logfile. Can be None. 577d201506cSStephen Warren 578d201506cSStephen Warren Returns: 579d201506cSStephen Warren A RunAndLog object. 580e8debf39SStephen Warren """ 581d201506cSStephen Warren 582d201506cSStephen Warren return RunAndLog(self, name, chained_file) 583d201506cSStephen Warren 584d201506cSStephen Warren def write(self, stream, data, implicit=False): 585e8debf39SStephen Warren """Write stream data into the log file. 586d201506cSStephen Warren 587d201506cSStephen Warren This function should only be used by instances of LogfileStream or 588d201506cSStephen Warren RunAndLog. 589d201506cSStephen Warren 590d201506cSStephen Warren Args: 591d201506cSStephen Warren stream: The stream whose data is being logged. 592d201506cSStephen Warren data: The data to log. 593d201506cSStephen Warren implicit: Boolean indicating whether data actually appeared in the 594d201506cSStephen Warren stream, or was implicitly generated. A valid use-case is to 595d201506cSStephen Warren repeat a shell prompt at the start of each separate log 596d201506cSStephen Warren section, which makes the log sections more readable in 597d201506cSStephen Warren isolation. 598d201506cSStephen Warren 599d201506cSStephen Warren Returns: 600d201506cSStephen Warren Nothing. 601e8debf39SStephen Warren """ 602d201506cSStephen Warren 603d201506cSStephen Warren if stream != self.last_stream: 604d201506cSStephen Warren self._terminate_stream() 605*83357fd5SStephen Warren self.f.write('<div class="stream block">\n') 606*83357fd5SStephen Warren self.f.write('<div class="stream-header block-header">Stream: ' + 607*83357fd5SStephen Warren stream.name + '</div>\n') 608*83357fd5SStephen Warren self.f.write('<div class="stream-content block-content">\n') 609a2ec5606SStephen Warren self.f.write('<pre>') 610d201506cSStephen Warren if implicit: 611a2ec5606SStephen Warren self.f.write('<span class="implicit">') 612d201506cSStephen Warren self.f.write(self._escape(data)) 613d201506cSStephen Warren if implicit: 614a2ec5606SStephen Warren self.f.write('</span>') 615d201506cSStephen Warren self.last_stream = stream 616d201506cSStephen Warren 617d201506cSStephen Warren def flush(self): 618e8debf39SStephen Warren """Flush the log stream, to ensure correct log interleaving. 619d201506cSStephen Warren 620d201506cSStephen Warren Args: 621d201506cSStephen Warren None. 622d201506cSStephen Warren 623d201506cSStephen Warren Returns: 624d201506cSStephen Warren Nothing. 625e8debf39SStephen Warren """ 626d201506cSStephen Warren 627d201506cSStephen Warren self.f.flush() 628