blob: 68917eb0ea9644d4e922ebcba2a07065a6e8e7bf [file] [log] [blame]
Stephen Warrend2015062016-01-15 11:15:24 -07001# Copyright (c) 2015 Stephen Warren
2# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
3#
4# SPDX-License-Identifier: GPL-2.0
5
6# Generate an HTML-formatted log file containing multiple streams of data,
7# each represented in a well-delineated/-structured fashion.
8
9import cgi
10import os.path
11import shutil
12import subprocess
13
14mod_dir = os.path.dirname(os.path.abspath(__file__))
15
16class LogfileStream(object):
Stephen Warrene8debf32016-01-26 13:41:30 -070017 """A file-like object used to write a single logical stream of data into
Stephen Warrend2015062016-01-15 11:15:24 -070018 a multiplexed log file. Objects of this type should be created by factory
Stephen Warrene8debf32016-01-26 13:41:30 -070019 functions in the Logfile class rather than directly."""
Stephen Warrend2015062016-01-15 11:15:24 -070020
21 def __init__(self, logfile, name, chained_file):
Stephen Warrene8debf32016-01-26 13:41:30 -070022 """Initialize a new object.
Stephen Warrend2015062016-01-15 11:15:24 -070023
24 Args:
25 logfile: The Logfile object to log to.
26 name: The name of this log stream.
27 chained_file: The file-like object to which all stream data should be
28 logged to in addition to logfile. Can be None.
29
30 Returns:
31 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -070032 """
Stephen Warrend2015062016-01-15 11:15:24 -070033
34 self.logfile = logfile
35 self.name = name
36 self.chained_file = chained_file
37
38 def close(self):
Stephen Warrene8debf32016-01-26 13:41:30 -070039 """Dummy function so that this class is "file-like".
Stephen Warrend2015062016-01-15 11:15:24 -070040
41 Args:
42 None.
43
44 Returns:
45 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -070046 """
Stephen Warrend2015062016-01-15 11:15:24 -070047
48 pass
49
50 def write(self, data, implicit=False):
Stephen Warrene8debf32016-01-26 13:41:30 -070051 """Write data to the log stream.
Stephen Warrend2015062016-01-15 11:15:24 -070052
53 Args:
54 data: The data to write tot he file.
55 implicit: Boolean indicating whether data actually appeared in the
56 stream, or was implicitly generated. A valid use-case is to
57 repeat a shell prompt at the start of each separate log
58 section, which makes the log sections more readable in
59 isolation.
60
61 Returns:
62 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -070063 """
Stephen Warrend2015062016-01-15 11:15:24 -070064
65 self.logfile.write(self, data, implicit)
66 if self.chained_file:
67 self.chained_file.write(data)
68
69 def flush(self):
Stephen Warrene8debf32016-01-26 13:41:30 -070070 """Flush the log stream, to ensure correct log interleaving.
Stephen Warrend2015062016-01-15 11:15:24 -070071
72 Args:
73 None.
74
75 Returns:
76 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -070077 """
Stephen Warrend2015062016-01-15 11:15:24 -070078
79 self.logfile.flush()
80 if self.chained_file:
81 self.chained_file.flush()
82
83class RunAndLog(object):
Stephen Warrene8debf32016-01-26 13:41:30 -070084 """A utility object used to execute sub-processes and log their output to
Stephen Warrend2015062016-01-15 11:15:24 -070085 a multiplexed log file. Objects of this type should be created by factory
Stephen Warrene8debf32016-01-26 13:41:30 -070086 functions in the Logfile class rather than directly."""
Stephen Warrend2015062016-01-15 11:15:24 -070087
88 def __init__(self, logfile, name, chained_file):
Stephen Warrene8debf32016-01-26 13:41:30 -070089 """Initialize a new object.
Stephen Warrend2015062016-01-15 11:15:24 -070090
91 Args:
92 logfile: The Logfile object to log to.
93 name: The name of this log stream or sub-process.
94 chained_file: The file-like object to which all stream data should
95 be logged to in addition to logfile. Can be None.
96
97 Returns:
98 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -070099 """
Stephen Warrend2015062016-01-15 11:15:24 -0700100
101 self.logfile = logfile
102 self.name = name
103 self.chained_file = chained_file
104
105 def close(self):
Stephen Warrene8debf32016-01-26 13:41:30 -0700106 """Clean up any resources managed by this object."""
Stephen Warrend2015062016-01-15 11:15:24 -0700107 pass
108
Stephen Warren3f2faf72016-01-22 12:30:11 -0700109 def run(self, cmd, cwd=None, ignore_errors=False):
Stephen Warrene8debf32016-01-26 13:41:30 -0700110 """Run a command as a sub-process, and log the results.
Stephen Warrend2015062016-01-15 11:15:24 -0700111
112 Args:
113 cmd: The command to execute.
114 cwd: The directory to run the command in. Can be None to use the
115 current directory.
Stephen Warren3f2faf72016-01-22 12:30:11 -0700116 ignore_errors: Indicate whether to ignore errors. If True, the
117 function will simply return if the command cannot be executed
118 or exits with an error code, otherwise an exception will be
119 raised if such problems occur.
Stephen Warrend2015062016-01-15 11:15:24 -0700120
121 Returns:
122 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700123 """
Stephen Warrend2015062016-01-15 11:15:24 -0700124
Stephen Warrena2ec5602016-01-26 13:41:31 -0700125 msg = '+' + ' '.join(cmd) + '\n'
Stephen Warrend2015062016-01-15 11:15:24 -0700126 if self.chained_file:
127 self.chained_file.write(msg)
128 self.logfile.write(self, msg)
129
130 try:
131 p = subprocess.Popen(cmd, cwd=cwd,
132 stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
133 (stdout, stderr) = p.communicate()
134 output = ''
135 if stdout:
136 if stderr:
137 output += 'stdout:\n'
138 output += stdout
139 if stderr:
140 if stdout:
141 output += 'stderr:\n'
142 output += stderr
143 exit_status = p.returncode
144 exception = None
145 except subprocess.CalledProcessError as cpe:
146 output = cpe.output
147 exit_status = cpe.returncode
148 exception = cpe
149 except Exception as e:
150 output = ''
151 exit_status = 0
152 exception = e
153 if output and not output.endswith('\n'):
154 output += '\n'
Stephen Warren3f2faf72016-01-22 12:30:11 -0700155 if exit_status and not exception and not ignore_errors:
Stephen Warrend2015062016-01-15 11:15:24 -0700156 exception = Exception('Exit code: ' + str(exit_status))
157 if exception:
158 output += str(exception) + '\n'
159 self.logfile.write(self, output)
160 if self.chained_file:
161 self.chained_file.write(output)
162 if exception:
163 raise exception
164
165class SectionCtxMgr(object):
Stephen Warrene8debf32016-01-26 13:41:30 -0700166 """A context manager for Python's "with" statement, which allows a certain
Stephen Warrend2015062016-01-15 11:15:24 -0700167 portion of test code to be logged to a separate section of the log file.
168 Objects of this type should be created by factory functions in the Logfile
Stephen Warrene8debf32016-01-26 13:41:30 -0700169 class rather than directly."""
Stephen Warrend2015062016-01-15 11:15:24 -0700170
Stephen Warren83357fd2016-02-03 16:46:34 -0700171 def __init__(self, log, marker, anchor):
Stephen Warrene8debf32016-01-26 13:41:30 -0700172 """Initialize a new object.
Stephen Warrend2015062016-01-15 11:15:24 -0700173
174 Args:
175 log: The Logfile object to log to.
176 marker: The name of the nested log section.
Stephen Warren83357fd2016-02-03 16:46:34 -0700177 anchor: The anchor value to pass to start_section().
Stephen Warrend2015062016-01-15 11:15:24 -0700178
179 Returns:
180 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700181 """
Stephen Warrend2015062016-01-15 11:15:24 -0700182
183 self.log = log
184 self.marker = marker
Stephen Warren83357fd2016-02-03 16:46:34 -0700185 self.anchor = anchor
Stephen Warrend2015062016-01-15 11:15:24 -0700186
187 def __enter__(self):
Stephen Warren83357fd2016-02-03 16:46:34 -0700188 self.anchor = self.log.start_section(self.marker, self.anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700189
190 def __exit__(self, extype, value, traceback):
191 self.log.end_section(self.marker)
192
193class Logfile(object):
Stephen Warrene8debf32016-01-26 13:41:30 -0700194 """Generates an HTML-formatted log file containing multiple streams of
195 data, each represented in a well-delineated/-structured fashion."""
Stephen Warrend2015062016-01-15 11:15:24 -0700196
197 def __init__(self, fn):
Stephen Warrene8debf32016-01-26 13:41:30 -0700198 """Initialize a new object.
Stephen Warrend2015062016-01-15 11:15:24 -0700199
200 Args:
201 fn: The filename to write to.
202
203 Returns:
204 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700205 """
Stephen Warrend2015062016-01-15 11:15:24 -0700206
Stephen Warrena2ec5602016-01-26 13:41:31 -0700207 self.f = open(fn, 'wt')
Stephen Warrend2015062016-01-15 11:15:24 -0700208 self.last_stream = None
209 self.blocks = []
210 self.cur_evt = 1
Stephen Warren83357fd2016-02-03 16:46:34 -0700211 self.anchor = 0
212
Stephen Warrena2ec5602016-01-26 13:41:31 -0700213 shutil.copy(mod_dir + '/multiplexed_log.css', os.path.dirname(fn))
214 self.f.write('''\
Stephen Warrend2015062016-01-15 11:15:24 -0700215<html>
216<head>
217<link rel="stylesheet" type="text/css" href="multiplexed_log.css">
Stephen Warren83357fd2016-02-03 16:46:34 -0700218<script src="http://code.jquery.com/jquery.min.js"></script>
219<script>
220$(document).ready(function () {
221 // Copy status report HTML to start of log for easy access
222 sts = $(".block#status_report")[0].outerHTML;
223 $("tt").prepend(sts);
224
225 // Add expand/contract buttons to all block headers
226 btns = "<span class=\\\"block-expand hidden\\\">[+] </span>" +
227 "<span class=\\\"block-contract\\\">[-] </span>";
228 $(".block-header").prepend(btns);
229
230 // Pre-contract all blocks which passed, leaving only problem cases
231 // expanded, to highlight issues the user should look at.
232 // Only top-level blocks (sections) should have any status
233 passed_bcs = $(".block-content:has(.status-pass)");
234 // Some blocks might have multiple status entries (e.g. the status
235 // report), so take care not to hide blocks with partial success.
236 passed_bcs = passed_bcs.not(":has(.status-fail)");
237 passed_bcs = passed_bcs.not(":has(.status-xfail)");
238 passed_bcs = passed_bcs.not(":has(.status-xpass)");
239 passed_bcs = passed_bcs.not(":has(.status-skipped)");
240 // Hide the passed blocks
241 passed_bcs.addClass("hidden");
242 // Flip the expand/contract button hiding for those blocks.
243 bhs = passed_bcs.parent().children(".block-header")
244 bhs.children(".block-expand").removeClass("hidden");
245 bhs.children(".block-contract").addClass("hidden");
246
247 // Add click handler to block headers.
248 // The handler expands/contracts the block.
249 $(".block-header").on("click", function (e) {
250 var header = $(this);
251 var content = header.next(".block-content");
252 var expanded = !content.hasClass("hidden");
253 if (expanded) {
254 content.addClass("hidden");
255 header.children(".block-expand").first().removeClass("hidden");
256 header.children(".block-contract").first().addClass("hidden");
257 } else {
258 header.children(".block-contract").first().removeClass("hidden");
259 header.children(".block-expand").first().addClass("hidden");
260 content.removeClass("hidden");
261 }
262 });
263
264 // When clicking on a link, expand the target block
265 $("a").on("click", function (e) {
266 var block = $($(this).attr("href"));
267 var header = block.children(".block-header");
268 var content = block.children(".block-content").first();
269 header.children(".block-contract").first().removeClass("hidden");
270 header.children(".block-expand").first().addClass("hidden");
271 content.removeClass("hidden");
272 });
273});
274</script>
Stephen Warrend2015062016-01-15 11:15:24 -0700275</head>
276<body>
277<tt>
Stephen Warrena2ec5602016-01-26 13:41:31 -0700278''')
Stephen Warrend2015062016-01-15 11:15:24 -0700279
280 def close(self):
Stephen Warrene8debf32016-01-26 13:41:30 -0700281 """Close the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700282
283 After calling this function, no more data may be written to the log.
284
285 Args:
286 None.
287
288 Returns:
289 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700290 """
Stephen Warrend2015062016-01-15 11:15:24 -0700291
Stephen Warrena2ec5602016-01-26 13:41:31 -0700292 self.f.write('''\
Stephen Warrend2015062016-01-15 11:15:24 -0700293</tt>
294</body>
295</html>
Stephen Warrena2ec5602016-01-26 13:41:31 -0700296''')
Stephen Warrend2015062016-01-15 11:15:24 -0700297 self.f.close()
298
299 # The set of characters that should be represented as hexadecimal codes in
300 # the log file.
Stephen Warrena2ec5602016-01-26 13:41:31 -0700301 _nonprint = ('%' + ''.join(chr(c) for c in range(0, 32) if c not in (9, 10)) +
302 ''.join(chr(c) for c in range(127, 256)))
Stephen Warrend2015062016-01-15 11:15:24 -0700303
304 def _escape(self, data):
Stephen Warrene8debf32016-01-26 13:41:30 -0700305 """Render data format suitable for inclusion in an HTML document.
Stephen Warrend2015062016-01-15 11:15:24 -0700306
307 This includes HTML-escaping certain characters, and translating
308 control characters to a hexadecimal representation.
309
310 Args:
311 data: The raw string data to be escaped.
312
313 Returns:
314 An escaped version of the data.
Stephen Warrene8debf32016-01-26 13:41:30 -0700315 """
Stephen Warrend2015062016-01-15 11:15:24 -0700316
Stephen Warrena2ec5602016-01-26 13:41:31 -0700317 data = data.replace(chr(13), '')
318 data = ''.join((c in self._nonprint) and ('%%%02x' % ord(c)) or
Stephen Warrend2015062016-01-15 11:15:24 -0700319 c for c in data)
320 data = cgi.escape(data)
321 return data
322
323 def _terminate_stream(self):
Stephen Warrene8debf32016-01-26 13:41:30 -0700324 """Write HTML to the log file to terminate the current stream's data.
Stephen Warrend2015062016-01-15 11:15:24 -0700325
326 Args:
327 None.
328
329 Returns:
330 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700331 """
Stephen Warrend2015062016-01-15 11:15:24 -0700332
333 self.cur_evt += 1
334 if not self.last_stream:
335 return
Stephen Warrena2ec5602016-01-26 13:41:31 -0700336 self.f.write('</pre>\n')
Stephen Warren83357fd2016-02-03 16:46:34 -0700337 self.f.write('<div class="stream-trailer block-trailer">End stream: ' +
Stephen Warrena2ec5602016-01-26 13:41:31 -0700338 self.last_stream.name + '</div>\n')
339 self.f.write('</div>\n')
Stephen Warren83357fd2016-02-03 16:46:34 -0700340 self.f.write('</div>\n')
Stephen Warrend2015062016-01-15 11:15:24 -0700341 self.last_stream = None
342
Stephen Warren83357fd2016-02-03 16:46:34 -0700343 def _note(self, note_type, msg, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700344 """Write a note or one-off message to the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700345
346 Args:
347 note_type: The type of note. This must be a value supported by the
348 accompanying multiplexed_log.css.
349 msg: The note/message to log.
Stephen Warren83357fd2016-02-03 16:46:34 -0700350 anchor: Optional internal link target.
Stephen Warrend2015062016-01-15 11:15:24 -0700351
352 Returns:
353 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700354 """
Stephen Warrend2015062016-01-15 11:15:24 -0700355
356 self._terminate_stream()
Stephen Warren83357fd2016-02-03 16:46:34 -0700357 self.f.write('<div class="' + note_type + '">\n')
358 if anchor:
359 self.f.write('<a href="#%s">\n' % anchor)
360 self.f.write('<pre>')
Stephen Warrend2015062016-01-15 11:15:24 -0700361 self.f.write(self._escape(msg))
Stephen Warren83357fd2016-02-03 16:46:34 -0700362 self.f.write('\n</pre>\n')
363 if anchor:
364 self.f.write('</a>\n')
365 self.f.write('</div>\n')
Stephen Warrend2015062016-01-15 11:15:24 -0700366
Stephen Warren83357fd2016-02-03 16:46:34 -0700367 def start_section(self, marker, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700368 """Begin a new nested section in the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700369
370 Args:
371 marker: The name of the section that is starting.
Stephen Warren83357fd2016-02-03 16:46:34 -0700372 anchor: The value to use for the anchor. If None, a unique value
373 will be calculated and used
Stephen Warrend2015062016-01-15 11:15:24 -0700374
375 Returns:
Stephen Warren83357fd2016-02-03 16:46:34 -0700376 Name of the HTML anchor emitted before section.
Stephen Warrene8debf32016-01-26 13:41:30 -0700377 """
Stephen Warrend2015062016-01-15 11:15:24 -0700378
379 self._terminate_stream()
380 self.blocks.append(marker)
Stephen Warren83357fd2016-02-03 16:46:34 -0700381 if not anchor:
382 self.anchor += 1
383 anchor = str(self.anchor)
Stephen Warrena2ec5602016-01-26 13:41:31 -0700384 blk_path = '/'.join(self.blocks)
Stephen Warren83357fd2016-02-03 16:46:34 -0700385 self.f.write('<div class="section block" id="' + anchor + '">\n')
386 self.f.write('<div class="section-header block-header">Section: ' +
387 blk_path + '</div>\n')
388 self.f.write('<div class="section-content block-content">\n')
389
390 return anchor
Stephen Warrend2015062016-01-15 11:15:24 -0700391
392 def end_section(self, marker):
Stephen Warrene8debf32016-01-26 13:41:30 -0700393 """Terminate the current nested section in the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700394
395 This function validates proper nesting of start_section() and
396 end_section() calls. If a mismatch is found, an exception is raised.
397
398 Args:
399 marker: The name of the section that is ending.
400
401 Returns:
402 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700403 """
Stephen Warrend2015062016-01-15 11:15:24 -0700404
405 if (not self.blocks) or (marker != self.blocks[-1]):
Stephen Warrena2ec5602016-01-26 13:41:31 -0700406 raise Exception('Block nesting mismatch: "%s" "%s"' %
407 (marker, '/'.join(self.blocks)))
Stephen Warrend2015062016-01-15 11:15:24 -0700408 self._terminate_stream()
Stephen Warrena2ec5602016-01-26 13:41:31 -0700409 blk_path = '/'.join(self.blocks)
Stephen Warren83357fd2016-02-03 16:46:34 -0700410 self.f.write('<div class="section-trailer block-trailer">' +
411 'End section: ' + blk_path + '</div>\n')
412 self.f.write('</div>\n')
Stephen Warrena2ec5602016-01-26 13:41:31 -0700413 self.f.write('</div>\n')
Stephen Warrend2015062016-01-15 11:15:24 -0700414 self.blocks.pop()
415
Stephen Warren83357fd2016-02-03 16:46:34 -0700416 def section(self, marker, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700417 """Create a temporary section in the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700418
419 This function creates a context manager for Python's "with" statement,
420 which allows a certain portion of test code to be logged to a separate
421 section of the log file.
422
423 Usage:
424 with log.section("somename"):
425 some test code
426
427 Args:
428 marker: The name of the nested section.
Stephen Warren83357fd2016-02-03 16:46:34 -0700429 anchor: The anchor value to pass to start_section().
Stephen Warrend2015062016-01-15 11:15:24 -0700430
431 Returns:
432 A context manager object.
Stephen Warrene8debf32016-01-26 13:41:30 -0700433 """
Stephen Warrend2015062016-01-15 11:15:24 -0700434
Stephen Warren83357fd2016-02-03 16:46:34 -0700435 return SectionCtxMgr(self, marker, anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700436
437 def error(self, msg):
Stephen Warrene8debf32016-01-26 13:41:30 -0700438 """Write an error note to the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700439
440 Args:
441 msg: A message describing the error.
442
443 Returns:
444 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700445 """
Stephen Warrend2015062016-01-15 11:15:24 -0700446
447 self._note("error", msg)
448
449 def warning(self, msg):
Stephen Warrene8debf32016-01-26 13:41:30 -0700450 """Write an warning note to the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700451
452 Args:
453 msg: A message describing the warning.
454
455 Returns:
456 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700457 """
Stephen Warrend2015062016-01-15 11:15:24 -0700458
459 self._note("warning", msg)
460
461 def info(self, msg):
Stephen Warrene8debf32016-01-26 13:41:30 -0700462 """Write an informational note to the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700463
464 Args:
465 msg: An informational message.
466
467 Returns:
468 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700469 """
Stephen Warrend2015062016-01-15 11:15:24 -0700470
471 self._note("info", msg)
472
473 def action(self, msg):
Stephen Warrene8debf32016-01-26 13:41:30 -0700474 """Write an action note to the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700475
476 Args:
477 msg: A message describing the action that is being logged.
478
479 Returns:
480 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700481 """
Stephen Warrend2015062016-01-15 11:15:24 -0700482
483 self._note("action", msg)
484
Stephen Warren83357fd2016-02-03 16:46:34 -0700485 def status_pass(self, msg, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700486 """Write a note to the log file describing test(s) which passed.
Stephen Warrend2015062016-01-15 11:15:24 -0700487
488 Args:
Stephen Warren78b39cc2016-01-27 23:57:51 -0700489 msg: A message describing the passed test(s).
Stephen Warren83357fd2016-02-03 16:46:34 -0700490 anchor: Optional internal link target.
Stephen Warrend2015062016-01-15 11:15:24 -0700491
492 Returns:
493 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700494 """
Stephen Warrend2015062016-01-15 11:15:24 -0700495
Stephen Warren83357fd2016-02-03 16:46:34 -0700496 self._note("status-pass", msg, anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700497
Stephen Warren83357fd2016-02-03 16:46:34 -0700498 def status_skipped(self, msg, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700499 """Write a note to the log file describing skipped test(s).
Stephen Warrend2015062016-01-15 11:15:24 -0700500
501 Args:
Stephen Warren78b39cc2016-01-27 23:57:51 -0700502 msg: A message describing the skipped test(s).
Stephen Warren83357fd2016-02-03 16:46:34 -0700503 anchor: Optional internal link target.
Stephen Warrend2015062016-01-15 11:15:24 -0700504
505 Returns:
506 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700507 """
Stephen Warrend2015062016-01-15 11:15:24 -0700508
Stephen Warren83357fd2016-02-03 16:46:34 -0700509 self._note("status-skipped", msg, anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700510
Stephen Warren83357fd2016-02-03 16:46:34 -0700511 def status_xfail(self, msg, anchor=None):
Stephen Warren78b39cc2016-01-27 23:57:51 -0700512 """Write a note to the log file describing xfailed test(s).
513
514 Args:
515 msg: A message describing the xfailed test(s).
Stephen Warren83357fd2016-02-03 16:46:34 -0700516 anchor: Optional internal link target.
Stephen Warren78b39cc2016-01-27 23:57:51 -0700517
518 Returns:
519 Nothing.
520 """
521
Stephen Warren83357fd2016-02-03 16:46:34 -0700522 self._note("status-xfail", msg, anchor)
Stephen Warren78b39cc2016-01-27 23:57:51 -0700523
Stephen Warren83357fd2016-02-03 16:46:34 -0700524 def status_xpass(self, msg, anchor=None):
Stephen Warren78b39cc2016-01-27 23:57:51 -0700525 """Write a note to the log file describing xpassed test(s).
526
527 Args:
528 msg: A message describing the xpassed test(s).
Stephen Warren83357fd2016-02-03 16:46:34 -0700529 anchor: Optional internal link target.
Stephen Warren78b39cc2016-01-27 23:57:51 -0700530
531 Returns:
532 Nothing.
533 """
534
Stephen Warren83357fd2016-02-03 16:46:34 -0700535 self._note("status-xpass", msg, anchor)
Stephen Warren78b39cc2016-01-27 23:57:51 -0700536
Stephen Warren83357fd2016-02-03 16:46:34 -0700537 def status_fail(self, msg, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700538 """Write a note to the log file describing failed test(s).
Stephen Warrend2015062016-01-15 11:15:24 -0700539
540 Args:
Stephen Warren78b39cc2016-01-27 23:57:51 -0700541 msg: A message describing the failed test(s).
Stephen Warren83357fd2016-02-03 16:46:34 -0700542 anchor: Optional internal link target.
Stephen Warrend2015062016-01-15 11:15:24 -0700543
544 Returns:
545 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700546 """
Stephen Warrend2015062016-01-15 11:15:24 -0700547
Stephen Warren83357fd2016-02-03 16:46:34 -0700548 self._note("status-fail", msg, anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700549
550 def get_stream(self, name, chained_file=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700551 """Create an object to log a single stream's data into the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700552
553 This creates a "file-like" object that can be written to in order to
554 write a single stream's data to the log file. The implementation will
555 handle any required interleaving of data (from multiple streams) in
556 the log, in a way that makes it obvious which stream each bit of data
557 came from.
558
559 Args:
560 name: The name of the stream.
561 chained_file: The file-like object to which all stream data should
562 be logged to in addition to this log. Can be None.
563
564 Returns:
565 A file-like object.
Stephen Warrene8debf32016-01-26 13:41:30 -0700566 """
Stephen Warrend2015062016-01-15 11:15:24 -0700567
568 return LogfileStream(self, name, chained_file)
569
570 def get_runner(self, name, chained_file=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700571 """Create an object that executes processes and logs their output.
Stephen Warrend2015062016-01-15 11:15:24 -0700572
573 Args:
574 name: The name of this sub-process.
575 chained_file: The file-like object to which all stream data should
576 be logged to in addition to logfile. Can be None.
577
578 Returns:
579 A RunAndLog object.
Stephen Warrene8debf32016-01-26 13:41:30 -0700580 """
Stephen Warrend2015062016-01-15 11:15:24 -0700581
582 return RunAndLog(self, name, chained_file)
583
584 def write(self, stream, data, implicit=False):
Stephen Warrene8debf32016-01-26 13:41:30 -0700585 """Write stream data into the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700586
587 This function should only be used by instances of LogfileStream or
588 RunAndLog.
589
590 Args:
591 stream: The stream whose data is being logged.
592 data: The data to log.
593 implicit: Boolean indicating whether data actually appeared in the
594 stream, or was implicitly generated. A valid use-case is to
595 repeat a shell prompt at the start of each separate log
596 section, which makes the log sections more readable in
597 isolation.
598
599 Returns:
600 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700601 """
Stephen Warrend2015062016-01-15 11:15:24 -0700602
603 if stream != self.last_stream:
604 self._terminate_stream()
Stephen Warren83357fd2016-02-03 16:46:34 -0700605 self.f.write('<div class="stream block">\n')
606 self.f.write('<div class="stream-header block-header">Stream: ' +
607 stream.name + '</div>\n')
608 self.f.write('<div class="stream-content block-content">\n')
Stephen Warrena2ec5602016-01-26 13:41:31 -0700609 self.f.write('<pre>')
Stephen Warrend2015062016-01-15 11:15:24 -0700610 if implicit:
Stephen Warrena2ec5602016-01-26 13:41:31 -0700611 self.f.write('<span class="implicit">')
Stephen Warrend2015062016-01-15 11:15:24 -0700612 self.f.write(self._escape(data))
613 if implicit:
Stephen Warrena2ec5602016-01-26 13:41:31 -0700614 self.f.write('</span>')
Stephen Warrend2015062016-01-15 11:15:24 -0700615 self.last_stream = stream
616
617 def flush(self):
Stephen Warrene8debf32016-01-26 13:41:30 -0700618 """Flush the log stream, to ensure correct log interleaving.
Stephen Warrend2015062016-01-15 11:15:24 -0700619
620 Args:
621 None.
622
623 Returns:
624 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700625 """
Stephen Warrend2015062016-01-15 11:15:24 -0700626
627 self.f.flush()