blob: 02c44df883aedc919e479f7ef347bf6a68d08513 [file] [log] [blame]
Stephen Warren10e50632016-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 Warren75e731e2016-01-26 13:41:30 -070017 """A file-like object used to write a single logical stream of data into
Stephen Warren10e50632016-01-15 11:15:24 -070018 a multiplexed log file. Objects of this type should be created by factory
Stephen Warren75e731e2016-01-26 13:41:30 -070019 functions in the Logfile class rather than directly."""
Stephen Warren10e50632016-01-15 11:15:24 -070020
21 def __init__(self, logfile, name, chained_file):
Stephen Warren75e731e2016-01-26 13:41:30 -070022 """Initialize a new object.
Stephen Warren10e50632016-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 Warren75e731e2016-01-26 13:41:30 -070032 """
Stephen Warren10e50632016-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 Warren75e731e2016-01-26 13:41:30 -070039 """Dummy function so that this class is "file-like".
Stephen Warren10e50632016-01-15 11:15:24 -070040
41 Args:
42 None.
43
44 Returns:
45 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -070046 """
Stephen Warren10e50632016-01-15 11:15:24 -070047
48 pass
49
50 def write(self, data, implicit=False):
Stephen Warren75e731e2016-01-26 13:41:30 -070051 """Write data to the log stream.
Stephen Warren10e50632016-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 Warren75e731e2016-01-26 13:41:30 -070063 """
Stephen Warren10e50632016-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 Warren75e731e2016-01-26 13:41:30 -070070 """Flush the log stream, to ensure correct log interleaving.
Stephen Warren10e50632016-01-15 11:15:24 -070071
72 Args:
73 None.
74
75 Returns:
76 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -070077 """
Stephen Warren10e50632016-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 Warren75e731e2016-01-26 13:41:30 -070084 """A utility object used to execute sub-processes and log their output to
Stephen Warren10e50632016-01-15 11:15:24 -070085 a multiplexed log file. Objects of this type should be created by factory
Stephen Warren75e731e2016-01-26 13:41:30 -070086 functions in the Logfile class rather than directly."""
Stephen Warren10e50632016-01-15 11:15:24 -070087
88 def __init__(self, logfile, name, chained_file):
Stephen Warren75e731e2016-01-26 13:41:30 -070089 """Initialize a new object.
Stephen Warren10e50632016-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 Warren75e731e2016-01-26 13:41:30 -070099 """
Stephen Warren10e50632016-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 Warren75e731e2016-01-26 13:41:30 -0700106 """Clean up any resources managed by this object."""
Stephen Warren10e50632016-01-15 11:15:24 -0700107 pass
108
Stephen Warren118e37e2016-01-22 12:30:11 -0700109 def run(self, cmd, cwd=None, ignore_errors=False):
Stephen Warren75e731e2016-01-26 13:41:30 -0700110 """Run a command as a sub-process, and log the results.
Stephen Warren10e50632016-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 Warren118e37e2016-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 Warren10e50632016-01-15 11:15:24 -0700120
121 Returns:
Simon Glass9f3c9e92016-07-03 09:40:37 -0600122 The output as a string.
Stephen Warren75e731e2016-01-26 13:41:30 -0700123 """
Stephen Warren10e50632016-01-15 11:15:24 -0700124
Stephen Warren3deb8962016-01-26 13:41:31 -0700125 msg = '+' + ' '.join(cmd) + '\n'
Stephen Warren10e50632016-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 Warren118e37e2016-01-22 12:30:11 -0700155 if exit_status and not exception and not ignore_errors:
Stephen Warren10e50632016-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
Simon Glass9f3c9e92016-07-03 09:40:37 -0600164 return output
Stephen Warren10e50632016-01-15 11:15:24 -0700165
166class SectionCtxMgr(object):
Stephen Warren75e731e2016-01-26 13:41:30 -0700167 """A context manager for Python's "with" statement, which allows a certain
Stephen Warren10e50632016-01-15 11:15:24 -0700168 portion of test code to be logged to a separate section of the log file.
169 Objects of this type should be created by factory functions in the Logfile
Stephen Warren75e731e2016-01-26 13:41:30 -0700170 class rather than directly."""
Stephen Warren10e50632016-01-15 11:15:24 -0700171
Stephen Warrene3f2a502016-02-03 16:46:34 -0700172 def __init__(self, log, marker, anchor):
Stephen Warren75e731e2016-01-26 13:41:30 -0700173 """Initialize a new object.
Stephen Warren10e50632016-01-15 11:15:24 -0700174
175 Args:
176 log: The Logfile object to log to.
177 marker: The name of the nested log section.
Stephen Warrene3f2a502016-02-03 16:46:34 -0700178 anchor: The anchor value to pass to start_section().
Stephen Warren10e50632016-01-15 11:15:24 -0700179
180 Returns:
181 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700182 """
Stephen Warren10e50632016-01-15 11:15:24 -0700183
184 self.log = log
185 self.marker = marker
Stephen Warrene3f2a502016-02-03 16:46:34 -0700186 self.anchor = anchor
Stephen Warren10e50632016-01-15 11:15:24 -0700187
188 def __enter__(self):
Stephen Warrene3f2a502016-02-03 16:46:34 -0700189 self.anchor = self.log.start_section(self.marker, self.anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700190
191 def __exit__(self, extype, value, traceback):
192 self.log.end_section(self.marker)
193
194class Logfile(object):
Stephen Warren75e731e2016-01-26 13:41:30 -0700195 """Generates an HTML-formatted log file containing multiple streams of
196 data, each represented in a well-delineated/-structured fashion."""
Stephen Warren10e50632016-01-15 11:15:24 -0700197
198 def __init__(self, fn):
Stephen Warren75e731e2016-01-26 13:41:30 -0700199 """Initialize a new object.
Stephen Warren10e50632016-01-15 11:15:24 -0700200
201 Args:
202 fn: The filename to write to.
203
204 Returns:
205 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700206 """
Stephen Warren10e50632016-01-15 11:15:24 -0700207
Stephen Warren3deb8962016-01-26 13:41:31 -0700208 self.f = open(fn, 'wt')
Stephen Warren10e50632016-01-15 11:15:24 -0700209 self.last_stream = None
210 self.blocks = []
211 self.cur_evt = 1
Stephen Warrene3f2a502016-02-03 16:46:34 -0700212 self.anchor = 0
213
Stephen Warren3deb8962016-01-26 13:41:31 -0700214 shutil.copy(mod_dir + '/multiplexed_log.css', os.path.dirname(fn))
215 self.f.write('''\
Stephen Warren10e50632016-01-15 11:15:24 -0700216<html>
217<head>
218<link rel="stylesheet" type="text/css" href="multiplexed_log.css">
Stephen Warrene3f2a502016-02-03 16:46:34 -0700219<script src="http://code.jquery.com/jquery.min.js"></script>
220<script>
221$(document).ready(function () {
222 // Copy status report HTML to start of log for easy access
223 sts = $(".block#status_report")[0].outerHTML;
224 $("tt").prepend(sts);
225
226 // Add expand/contract buttons to all block headers
227 btns = "<span class=\\\"block-expand hidden\\\">[+] </span>" +
228 "<span class=\\\"block-contract\\\">[-] </span>";
229 $(".block-header").prepend(btns);
230
231 // Pre-contract all blocks which passed, leaving only problem cases
232 // expanded, to highlight issues the user should look at.
233 // Only top-level blocks (sections) should have any status
234 passed_bcs = $(".block-content:has(.status-pass)");
235 // Some blocks might have multiple status entries (e.g. the status
236 // report), so take care not to hide blocks with partial success.
237 passed_bcs = passed_bcs.not(":has(.status-fail)");
238 passed_bcs = passed_bcs.not(":has(.status-xfail)");
239 passed_bcs = passed_bcs.not(":has(.status-xpass)");
240 passed_bcs = passed_bcs.not(":has(.status-skipped)");
241 // Hide the passed blocks
242 passed_bcs.addClass("hidden");
243 // Flip the expand/contract button hiding for those blocks.
244 bhs = passed_bcs.parent().children(".block-header")
245 bhs.children(".block-expand").removeClass("hidden");
246 bhs.children(".block-contract").addClass("hidden");
247
248 // Add click handler to block headers.
249 // The handler expands/contracts the block.
250 $(".block-header").on("click", function (e) {
251 var header = $(this);
252 var content = header.next(".block-content");
253 var expanded = !content.hasClass("hidden");
254 if (expanded) {
255 content.addClass("hidden");
256 header.children(".block-expand").first().removeClass("hidden");
257 header.children(".block-contract").first().addClass("hidden");
258 } else {
259 header.children(".block-contract").first().removeClass("hidden");
260 header.children(".block-expand").first().addClass("hidden");
261 content.removeClass("hidden");
262 }
263 });
264
265 // When clicking on a link, expand the target block
266 $("a").on("click", function (e) {
267 var block = $($(this).attr("href"));
268 var header = block.children(".block-header");
269 var content = block.children(".block-content").first();
270 header.children(".block-contract").first().removeClass("hidden");
271 header.children(".block-expand").first().addClass("hidden");
272 content.removeClass("hidden");
273 });
274});
275</script>
Stephen Warren10e50632016-01-15 11:15:24 -0700276</head>
277<body>
278<tt>
Stephen Warren3deb8962016-01-26 13:41:31 -0700279''')
Stephen Warren10e50632016-01-15 11:15:24 -0700280
281 def close(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700282 """Close the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700283
284 After calling this function, no more data may be written to the log.
285
286 Args:
287 None.
288
289 Returns:
290 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700291 """
Stephen Warren10e50632016-01-15 11:15:24 -0700292
Stephen Warren3deb8962016-01-26 13:41:31 -0700293 self.f.write('''\
Stephen Warren10e50632016-01-15 11:15:24 -0700294</tt>
295</body>
296</html>
Stephen Warren3deb8962016-01-26 13:41:31 -0700297''')
Stephen Warren10e50632016-01-15 11:15:24 -0700298 self.f.close()
299
300 # The set of characters that should be represented as hexadecimal codes in
301 # the log file.
Stephen Warren3deb8962016-01-26 13:41:31 -0700302 _nonprint = ('%' + ''.join(chr(c) for c in range(0, 32) if c not in (9, 10)) +
303 ''.join(chr(c) for c in range(127, 256)))
Stephen Warren10e50632016-01-15 11:15:24 -0700304
305 def _escape(self, data):
Stephen Warren75e731e2016-01-26 13:41:30 -0700306 """Render data format suitable for inclusion in an HTML document.
Stephen Warren10e50632016-01-15 11:15:24 -0700307
308 This includes HTML-escaping certain characters, and translating
309 control characters to a hexadecimal representation.
310
311 Args:
312 data: The raw string data to be escaped.
313
314 Returns:
315 An escaped version of the data.
Stephen Warren75e731e2016-01-26 13:41:30 -0700316 """
Stephen Warren10e50632016-01-15 11:15:24 -0700317
Stephen Warren3deb8962016-01-26 13:41:31 -0700318 data = data.replace(chr(13), '')
319 data = ''.join((c in self._nonprint) and ('%%%02x' % ord(c)) or
Stephen Warren10e50632016-01-15 11:15:24 -0700320 c for c in data)
321 data = cgi.escape(data)
322 return data
323
324 def _terminate_stream(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700325 """Write HTML to the log file to terminate the current stream's data.
Stephen Warren10e50632016-01-15 11:15:24 -0700326
327 Args:
328 None.
329
330 Returns:
331 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700332 """
Stephen Warren10e50632016-01-15 11:15:24 -0700333
334 self.cur_evt += 1
335 if not self.last_stream:
336 return
Stephen Warren3deb8962016-01-26 13:41:31 -0700337 self.f.write('</pre>\n')
Stephen Warrene3f2a502016-02-03 16:46:34 -0700338 self.f.write('<div class="stream-trailer block-trailer">End stream: ' +
Stephen Warren3deb8962016-01-26 13:41:31 -0700339 self.last_stream.name + '</div>\n')
340 self.f.write('</div>\n')
Stephen Warrene3f2a502016-02-03 16:46:34 -0700341 self.f.write('</div>\n')
Stephen Warren10e50632016-01-15 11:15:24 -0700342 self.last_stream = None
343
Stephen Warrene3f2a502016-02-03 16:46:34 -0700344 def _note(self, note_type, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700345 """Write a note or one-off message to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700346
347 Args:
348 note_type: The type of note. This must be a value supported by the
349 accompanying multiplexed_log.css.
350 msg: The note/message to log.
Stephen Warrene3f2a502016-02-03 16:46:34 -0700351 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700352
353 Returns:
354 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700355 """
Stephen Warren10e50632016-01-15 11:15:24 -0700356
357 self._terminate_stream()
Stephen Warrene3f2a502016-02-03 16:46:34 -0700358 self.f.write('<div class="' + note_type + '">\n')
359 if anchor:
360 self.f.write('<a href="#%s">\n' % anchor)
361 self.f.write('<pre>')
Stephen Warren10e50632016-01-15 11:15:24 -0700362 self.f.write(self._escape(msg))
Stephen Warrene3f2a502016-02-03 16:46:34 -0700363 self.f.write('\n</pre>\n')
364 if anchor:
365 self.f.write('</a>\n')
366 self.f.write('</div>\n')
Stephen Warren10e50632016-01-15 11:15:24 -0700367
Stephen Warrene3f2a502016-02-03 16:46:34 -0700368 def start_section(self, marker, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700369 """Begin a new nested section in the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700370
371 Args:
372 marker: The name of the section that is starting.
Stephen Warrene3f2a502016-02-03 16:46:34 -0700373 anchor: The value to use for the anchor. If None, a unique value
374 will be calculated and used
Stephen Warren10e50632016-01-15 11:15:24 -0700375
376 Returns:
Stephen Warrene3f2a502016-02-03 16:46:34 -0700377 Name of the HTML anchor emitted before section.
Stephen Warren75e731e2016-01-26 13:41:30 -0700378 """
Stephen Warren10e50632016-01-15 11:15:24 -0700379
380 self._terminate_stream()
381 self.blocks.append(marker)
Stephen Warrene3f2a502016-02-03 16:46:34 -0700382 if not anchor:
383 self.anchor += 1
384 anchor = str(self.anchor)
Stephen Warren3deb8962016-01-26 13:41:31 -0700385 blk_path = '/'.join(self.blocks)
Stephen Warrene3f2a502016-02-03 16:46:34 -0700386 self.f.write('<div class="section block" id="' + anchor + '">\n')
387 self.f.write('<div class="section-header block-header">Section: ' +
388 blk_path + '</div>\n')
389 self.f.write('<div class="section-content block-content">\n')
390
391 return anchor
Stephen Warren10e50632016-01-15 11:15:24 -0700392
393 def end_section(self, marker):
Stephen Warren75e731e2016-01-26 13:41:30 -0700394 """Terminate the current nested section in the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700395
396 This function validates proper nesting of start_section() and
397 end_section() calls. If a mismatch is found, an exception is raised.
398
399 Args:
400 marker: The name of the section that is ending.
401
402 Returns:
403 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700404 """
Stephen Warren10e50632016-01-15 11:15:24 -0700405
406 if (not self.blocks) or (marker != self.blocks[-1]):
Stephen Warren3deb8962016-01-26 13:41:31 -0700407 raise Exception('Block nesting mismatch: "%s" "%s"' %
408 (marker, '/'.join(self.blocks)))
Stephen Warren10e50632016-01-15 11:15:24 -0700409 self._terminate_stream()
Stephen Warren3deb8962016-01-26 13:41:31 -0700410 blk_path = '/'.join(self.blocks)
Stephen Warrene3f2a502016-02-03 16:46:34 -0700411 self.f.write('<div class="section-trailer block-trailer">' +
412 'End section: ' + blk_path + '</div>\n')
413 self.f.write('</div>\n')
Stephen Warren3deb8962016-01-26 13:41:31 -0700414 self.f.write('</div>\n')
Stephen Warren10e50632016-01-15 11:15:24 -0700415 self.blocks.pop()
416
Stephen Warrene3f2a502016-02-03 16:46:34 -0700417 def section(self, marker, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700418 """Create a temporary section in the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700419
420 This function creates a context manager for Python's "with" statement,
421 which allows a certain portion of test code to be logged to a separate
422 section of the log file.
423
424 Usage:
425 with log.section("somename"):
426 some test code
427
428 Args:
429 marker: The name of the nested section.
Stephen Warrene3f2a502016-02-03 16:46:34 -0700430 anchor: The anchor value to pass to start_section().
Stephen Warren10e50632016-01-15 11:15:24 -0700431
432 Returns:
433 A context manager object.
Stephen Warren75e731e2016-01-26 13:41:30 -0700434 """
Stephen Warren10e50632016-01-15 11:15:24 -0700435
Stephen Warrene3f2a502016-02-03 16:46:34 -0700436 return SectionCtxMgr(self, marker, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700437
438 def error(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700439 """Write an error note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700440
441 Args:
442 msg: A message describing the error.
443
444 Returns:
445 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700446 """
Stephen Warren10e50632016-01-15 11:15:24 -0700447
448 self._note("error", msg)
449
450 def warning(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700451 """Write an warning note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700452
453 Args:
454 msg: A message describing the warning.
455
456 Returns:
457 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700458 """
Stephen Warren10e50632016-01-15 11:15:24 -0700459
460 self._note("warning", msg)
461
462 def info(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700463 """Write an informational note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700464
465 Args:
466 msg: An informational message.
467
468 Returns:
469 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700470 """
Stephen Warren10e50632016-01-15 11:15:24 -0700471
472 self._note("info", msg)
473
474 def action(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700475 """Write an action note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700476
477 Args:
478 msg: A message describing the action that is being logged.
479
480 Returns:
481 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700482 """
Stephen Warren10e50632016-01-15 11:15:24 -0700483
484 self._note("action", msg)
485
Stephen Warrene3f2a502016-02-03 16:46:34 -0700486 def status_pass(self, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700487 """Write a note to the log file describing test(s) which passed.
Stephen Warren10e50632016-01-15 11:15:24 -0700488
489 Args:
Stephen Warren25b05242016-01-27 23:57:51 -0700490 msg: A message describing the passed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700491 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700492
493 Returns:
494 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700495 """
Stephen Warren10e50632016-01-15 11:15:24 -0700496
Stephen Warrene3f2a502016-02-03 16:46:34 -0700497 self._note("status-pass", msg, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700498
Stephen Warrene3f2a502016-02-03 16:46:34 -0700499 def status_skipped(self, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700500 """Write a note to the log file describing skipped test(s).
Stephen Warren10e50632016-01-15 11:15:24 -0700501
502 Args:
Stephen Warren25b05242016-01-27 23:57:51 -0700503 msg: A message describing the skipped test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700504 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700505
506 Returns:
507 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700508 """
Stephen Warren10e50632016-01-15 11:15:24 -0700509
Stephen Warrene3f2a502016-02-03 16:46:34 -0700510 self._note("status-skipped", msg, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700511
Stephen Warrene3f2a502016-02-03 16:46:34 -0700512 def status_xfail(self, msg, anchor=None):
Stephen Warren25b05242016-01-27 23:57:51 -0700513 """Write a note to the log file describing xfailed test(s).
514
515 Args:
516 msg: A message describing the xfailed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700517 anchor: Optional internal link target.
Stephen Warren25b05242016-01-27 23:57:51 -0700518
519 Returns:
520 Nothing.
521 """
522
Stephen Warrene3f2a502016-02-03 16:46:34 -0700523 self._note("status-xfail", msg, anchor)
Stephen Warren25b05242016-01-27 23:57:51 -0700524
Stephen Warrene3f2a502016-02-03 16:46:34 -0700525 def status_xpass(self, msg, anchor=None):
Stephen Warren25b05242016-01-27 23:57:51 -0700526 """Write a note to the log file describing xpassed test(s).
527
528 Args:
529 msg: A message describing the xpassed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700530 anchor: Optional internal link target.
Stephen Warren25b05242016-01-27 23:57:51 -0700531
532 Returns:
533 Nothing.
534 """
535
Stephen Warrene3f2a502016-02-03 16:46:34 -0700536 self._note("status-xpass", msg, anchor)
Stephen Warren25b05242016-01-27 23:57:51 -0700537
Stephen Warrene3f2a502016-02-03 16:46:34 -0700538 def status_fail(self, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700539 """Write a note to the log file describing failed test(s).
Stephen Warren10e50632016-01-15 11:15:24 -0700540
541 Args:
Stephen Warren25b05242016-01-27 23:57:51 -0700542 msg: A message describing the failed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700543 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700544
545 Returns:
546 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700547 """
Stephen Warren10e50632016-01-15 11:15:24 -0700548
Stephen Warrene3f2a502016-02-03 16:46:34 -0700549 self._note("status-fail", msg, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700550
551 def get_stream(self, name, chained_file=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700552 """Create an object to log a single stream's data into the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700553
554 This creates a "file-like" object that can be written to in order to
555 write a single stream's data to the log file. The implementation will
556 handle any required interleaving of data (from multiple streams) in
557 the log, in a way that makes it obvious which stream each bit of data
558 came from.
559
560 Args:
561 name: The name of the stream.
562 chained_file: The file-like object to which all stream data should
563 be logged to in addition to this log. Can be None.
564
565 Returns:
566 A file-like object.
Stephen Warren75e731e2016-01-26 13:41:30 -0700567 """
Stephen Warren10e50632016-01-15 11:15:24 -0700568
569 return LogfileStream(self, name, chained_file)
570
571 def get_runner(self, name, chained_file=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700572 """Create an object that executes processes and logs their output.
Stephen Warren10e50632016-01-15 11:15:24 -0700573
574 Args:
575 name: The name of this sub-process.
576 chained_file: The file-like object to which all stream data should
577 be logged to in addition to logfile. Can be None.
578
579 Returns:
580 A RunAndLog object.
Stephen Warren75e731e2016-01-26 13:41:30 -0700581 """
Stephen Warren10e50632016-01-15 11:15:24 -0700582
583 return RunAndLog(self, name, chained_file)
584
585 def write(self, stream, data, implicit=False):
Stephen Warren75e731e2016-01-26 13:41:30 -0700586 """Write stream data into the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700587
588 This function should only be used by instances of LogfileStream or
589 RunAndLog.
590
591 Args:
592 stream: The stream whose data is being logged.
593 data: The data to log.
594 implicit: Boolean indicating whether data actually appeared in the
595 stream, or was implicitly generated. A valid use-case is to
596 repeat a shell prompt at the start of each separate log
597 section, which makes the log sections more readable in
598 isolation.
599
600 Returns:
601 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700602 """
Stephen Warren10e50632016-01-15 11:15:24 -0700603
604 if stream != self.last_stream:
605 self._terminate_stream()
Stephen Warrene3f2a502016-02-03 16:46:34 -0700606 self.f.write('<div class="stream block">\n')
607 self.f.write('<div class="stream-header block-header">Stream: ' +
608 stream.name + '</div>\n')
609 self.f.write('<div class="stream-content block-content">\n')
Stephen Warren3deb8962016-01-26 13:41:31 -0700610 self.f.write('<pre>')
Stephen Warren10e50632016-01-15 11:15:24 -0700611 if implicit:
Stephen Warren3deb8962016-01-26 13:41:31 -0700612 self.f.write('<span class="implicit">')
Stephen Warren10e50632016-01-15 11:15:24 -0700613 self.f.write(self._escape(data))
614 if implicit:
Stephen Warren3deb8962016-01-26 13:41:31 -0700615 self.f.write('</span>')
Stephen Warren10e50632016-01-15 11:15:24 -0700616 self.last_stream = stream
617
618 def flush(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700619 """Flush the log stream, to ensure correct log interleaving.
Stephen Warren10e50632016-01-15 11:15:24 -0700620
621 Args:
622 None.
623
624 Returns:
625 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700626 """
Stephen Warren10e50632016-01-15 11:15:24 -0700627
628 self.f.flush()