blob: 35a32fb5c0514fdbd8e697680bac8e338b74edb9 [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
Simon Glass4134caf2016-07-03 09:40:38 -0600104 self.output = None
Stephen Warren10e50632016-01-15 11:15:24 -0700105
106 def close(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700107 """Clean up any resources managed by this object."""
Stephen Warren10e50632016-01-15 11:15:24 -0700108 pass
109
Stephen Warren118e37e2016-01-22 12:30:11 -0700110 def run(self, cmd, cwd=None, ignore_errors=False):
Stephen Warren75e731e2016-01-26 13:41:30 -0700111 """Run a command as a sub-process, and log the results.
Stephen Warren10e50632016-01-15 11:15:24 -0700112
Simon Glass4134caf2016-07-03 09:40:38 -0600113 The output is available at self.output which can be useful if there is
114 an exception.
115
Stephen Warren10e50632016-01-15 11:15:24 -0700116 Args:
117 cmd: The command to execute.
118 cwd: The directory to run the command in. Can be None to use the
119 current directory.
Stephen Warren118e37e2016-01-22 12:30:11 -0700120 ignore_errors: Indicate whether to ignore errors. If True, the
121 function will simply return if the command cannot be executed
122 or exits with an error code, otherwise an exception will be
123 raised if such problems occur.
Stephen Warren10e50632016-01-15 11:15:24 -0700124
125 Returns:
Simon Glass9f3c9e92016-07-03 09:40:37 -0600126 The output as a string.
Stephen Warren75e731e2016-01-26 13:41:30 -0700127 """
Stephen Warren10e50632016-01-15 11:15:24 -0700128
Stephen Warren3deb8962016-01-26 13:41:31 -0700129 msg = '+' + ' '.join(cmd) + '\n'
Stephen Warren10e50632016-01-15 11:15:24 -0700130 if self.chained_file:
131 self.chained_file.write(msg)
132 self.logfile.write(self, msg)
133
134 try:
135 p = subprocess.Popen(cmd, cwd=cwd,
136 stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
137 (stdout, stderr) = p.communicate()
138 output = ''
139 if stdout:
140 if stderr:
141 output += 'stdout:\n'
142 output += stdout
143 if stderr:
144 if stdout:
145 output += 'stderr:\n'
146 output += stderr
147 exit_status = p.returncode
148 exception = None
149 except subprocess.CalledProcessError as cpe:
150 output = cpe.output
151 exit_status = cpe.returncode
152 exception = cpe
153 except Exception as e:
154 output = ''
155 exit_status = 0
156 exception = e
157 if output and not output.endswith('\n'):
158 output += '\n'
Stephen Warren118e37e2016-01-22 12:30:11 -0700159 if exit_status and not exception and not ignore_errors:
Stephen Warren10e50632016-01-15 11:15:24 -0700160 exception = Exception('Exit code: ' + str(exit_status))
161 if exception:
162 output += str(exception) + '\n'
163 self.logfile.write(self, output)
164 if self.chained_file:
165 self.chained_file.write(output)
Simon Glass4134caf2016-07-03 09:40:38 -0600166
167 # Store the output so it can be accessed if we raise an exception.
168 self.output = output
Stephen Warren10e50632016-01-15 11:15:24 -0700169 if exception:
170 raise exception
Simon Glass9f3c9e92016-07-03 09:40:37 -0600171 return output
Stephen Warren10e50632016-01-15 11:15:24 -0700172
173class SectionCtxMgr(object):
Stephen Warren75e731e2016-01-26 13:41:30 -0700174 """A context manager for Python's "with" statement, which allows a certain
Stephen Warren10e50632016-01-15 11:15:24 -0700175 portion of test code to be logged to a separate section of the log file.
176 Objects of this type should be created by factory functions in the Logfile
Stephen Warren75e731e2016-01-26 13:41:30 -0700177 class rather than directly."""
Stephen Warren10e50632016-01-15 11:15:24 -0700178
Stephen Warrene3f2a502016-02-03 16:46:34 -0700179 def __init__(self, log, marker, anchor):
Stephen Warren75e731e2016-01-26 13:41:30 -0700180 """Initialize a new object.
Stephen Warren10e50632016-01-15 11:15:24 -0700181
182 Args:
183 log: The Logfile object to log to.
184 marker: The name of the nested log section.
Stephen Warrene3f2a502016-02-03 16:46:34 -0700185 anchor: The anchor value to pass to start_section().
Stephen Warren10e50632016-01-15 11:15:24 -0700186
187 Returns:
188 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700189 """
Stephen Warren10e50632016-01-15 11:15:24 -0700190
191 self.log = log
192 self.marker = marker
Stephen Warrene3f2a502016-02-03 16:46:34 -0700193 self.anchor = anchor
Stephen Warren10e50632016-01-15 11:15:24 -0700194
195 def __enter__(self):
Stephen Warrene3f2a502016-02-03 16:46:34 -0700196 self.anchor = self.log.start_section(self.marker, self.anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700197
198 def __exit__(self, extype, value, traceback):
199 self.log.end_section(self.marker)
200
201class Logfile(object):
Stephen Warren75e731e2016-01-26 13:41:30 -0700202 """Generates an HTML-formatted log file containing multiple streams of
203 data, each represented in a well-delineated/-structured fashion."""
Stephen Warren10e50632016-01-15 11:15:24 -0700204
205 def __init__(self, fn):
Stephen Warren75e731e2016-01-26 13:41:30 -0700206 """Initialize a new object.
Stephen Warren10e50632016-01-15 11:15:24 -0700207
208 Args:
209 fn: The filename to write to.
210
211 Returns:
212 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700213 """
Stephen Warren10e50632016-01-15 11:15:24 -0700214
Stephen Warren3deb8962016-01-26 13:41:31 -0700215 self.f = open(fn, 'wt')
Stephen Warren10e50632016-01-15 11:15:24 -0700216 self.last_stream = None
217 self.blocks = []
218 self.cur_evt = 1
Stephen Warrene3f2a502016-02-03 16:46:34 -0700219 self.anchor = 0
220
Stephen Warren3deb8962016-01-26 13:41:31 -0700221 shutil.copy(mod_dir + '/multiplexed_log.css', os.path.dirname(fn))
222 self.f.write('''\
Stephen Warren10e50632016-01-15 11:15:24 -0700223<html>
224<head>
225<link rel="stylesheet" type="text/css" href="multiplexed_log.css">
Stephen Warrene3f2a502016-02-03 16:46:34 -0700226<script src="http://code.jquery.com/jquery.min.js"></script>
227<script>
228$(document).ready(function () {
229 // Copy status report HTML to start of log for easy access
230 sts = $(".block#status_report")[0].outerHTML;
231 $("tt").prepend(sts);
232
233 // Add expand/contract buttons to all block headers
234 btns = "<span class=\\\"block-expand hidden\\\">[+] </span>" +
235 "<span class=\\\"block-contract\\\">[-] </span>";
236 $(".block-header").prepend(btns);
237
238 // Pre-contract all blocks which passed, leaving only problem cases
239 // expanded, to highlight issues the user should look at.
240 // Only top-level blocks (sections) should have any status
241 passed_bcs = $(".block-content:has(.status-pass)");
242 // Some blocks might have multiple status entries (e.g. the status
243 // report), so take care not to hide blocks with partial success.
244 passed_bcs = passed_bcs.not(":has(.status-fail)");
245 passed_bcs = passed_bcs.not(":has(.status-xfail)");
246 passed_bcs = passed_bcs.not(":has(.status-xpass)");
247 passed_bcs = passed_bcs.not(":has(.status-skipped)");
248 // Hide the passed blocks
249 passed_bcs.addClass("hidden");
250 // Flip the expand/contract button hiding for those blocks.
251 bhs = passed_bcs.parent().children(".block-header")
252 bhs.children(".block-expand").removeClass("hidden");
253 bhs.children(".block-contract").addClass("hidden");
254
255 // Add click handler to block headers.
256 // The handler expands/contracts the block.
257 $(".block-header").on("click", function (e) {
258 var header = $(this);
259 var content = header.next(".block-content");
260 var expanded = !content.hasClass("hidden");
261 if (expanded) {
262 content.addClass("hidden");
263 header.children(".block-expand").first().removeClass("hidden");
264 header.children(".block-contract").first().addClass("hidden");
265 } else {
266 header.children(".block-contract").first().removeClass("hidden");
267 header.children(".block-expand").first().addClass("hidden");
268 content.removeClass("hidden");
269 }
270 });
271
272 // When clicking on a link, expand the target block
273 $("a").on("click", function (e) {
274 var block = $($(this).attr("href"));
275 var header = block.children(".block-header");
276 var content = block.children(".block-content").first();
277 header.children(".block-contract").first().removeClass("hidden");
278 header.children(".block-expand").first().addClass("hidden");
279 content.removeClass("hidden");
280 });
281});
282</script>
Stephen Warren10e50632016-01-15 11:15:24 -0700283</head>
284<body>
285<tt>
Stephen Warren3deb8962016-01-26 13:41:31 -0700286''')
Stephen Warren10e50632016-01-15 11:15:24 -0700287
288 def close(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700289 """Close the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700290
291 After calling this function, no more data may be written to the log.
292
293 Args:
294 None.
295
296 Returns:
297 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700298 """
Stephen Warren10e50632016-01-15 11:15:24 -0700299
Stephen Warren3deb8962016-01-26 13:41:31 -0700300 self.f.write('''\
Stephen Warren10e50632016-01-15 11:15:24 -0700301</tt>
302</body>
303</html>
Stephen Warren3deb8962016-01-26 13:41:31 -0700304''')
Stephen Warren10e50632016-01-15 11:15:24 -0700305 self.f.close()
306
307 # The set of characters that should be represented as hexadecimal codes in
308 # the log file.
Stephen Warren3deb8962016-01-26 13:41:31 -0700309 _nonprint = ('%' + ''.join(chr(c) for c in range(0, 32) if c not in (9, 10)) +
310 ''.join(chr(c) for c in range(127, 256)))
Stephen Warren10e50632016-01-15 11:15:24 -0700311
312 def _escape(self, data):
Stephen Warren75e731e2016-01-26 13:41:30 -0700313 """Render data format suitable for inclusion in an HTML document.
Stephen Warren10e50632016-01-15 11:15:24 -0700314
315 This includes HTML-escaping certain characters, and translating
316 control characters to a hexadecimal representation.
317
318 Args:
319 data: The raw string data to be escaped.
320
321 Returns:
322 An escaped version of the data.
Stephen Warren75e731e2016-01-26 13:41:30 -0700323 """
Stephen Warren10e50632016-01-15 11:15:24 -0700324
Stephen Warren3deb8962016-01-26 13:41:31 -0700325 data = data.replace(chr(13), '')
326 data = ''.join((c in self._nonprint) and ('%%%02x' % ord(c)) or
Stephen Warren10e50632016-01-15 11:15:24 -0700327 c for c in data)
328 data = cgi.escape(data)
329 return data
330
331 def _terminate_stream(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700332 """Write HTML to the log file to terminate the current stream's data.
Stephen Warren10e50632016-01-15 11:15:24 -0700333
334 Args:
335 None.
336
337 Returns:
338 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700339 """
Stephen Warren10e50632016-01-15 11:15:24 -0700340
341 self.cur_evt += 1
342 if not self.last_stream:
343 return
Stephen Warren3deb8962016-01-26 13:41:31 -0700344 self.f.write('</pre>\n')
Stephen Warrene3f2a502016-02-03 16:46:34 -0700345 self.f.write('<div class="stream-trailer block-trailer">End stream: ' +
Stephen Warren3deb8962016-01-26 13:41:31 -0700346 self.last_stream.name + '</div>\n')
347 self.f.write('</div>\n')
Stephen Warrene3f2a502016-02-03 16:46:34 -0700348 self.f.write('</div>\n')
Stephen Warren10e50632016-01-15 11:15:24 -0700349 self.last_stream = None
350
Stephen Warrene3f2a502016-02-03 16:46:34 -0700351 def _note(self, note_type, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700352 """Write a note or one-off message to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700353
354 Args:
355 note_type: The type of note. This must be a value supported by the
356 accompanying multiplexed_log.css.
357 msg: The note/message to log.
Stephen Warrene3f2a502016-02-03 16:46:34 -0700358 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700359
360 Returns:
361 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700362 """
Stephen Warren10e50632016-01-15 11:15:24 -0700363
364 self._terminate_stream()
Stephen Warrene3f2a502016-02-03 16:46:34 -0700365 self.f.write('<div class="' + note_type + '">\n')
366 if anchor:
367 self.f.write('<a href="#%s">\n' % anchor)
368 self.f.write('<pre>')
Stephen Warren10e50632016-01-15 11:15:24 -0700369 self.f.write(self._escape(msg))
Stephen Warrene3f2a502016-02-03 16:46:34 -0700370 self.f.write('\n</pre>\n')
371 if anchor:
372 self.f.write('</a>\n')
373 self.f.write('</div>\n')
Stephen Warren10e50632016-01-15 11:15:24 -0700374
Stephen Warrene3f2a502016-02-03 16:46:34 -0700375 def start_section(self, marker, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700376 """Begin a new nested section in the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700377
378 Args:
379 marker: The name of the section that is starting.
Stephen Warrene3f2a502016-02-03 16:46:34 -0700380 anchor: The value to use for the anchor. If None, a unique value
381 will be calculated and used
Stephen Warren10e50632016-01-15 11:15:24 -0700382
383 Returns:
Stephen Warrene3f2a502016-02-03 16:46:34 -0700384 Name of the HTML anchor emitted before section.
Stephen Warren75e731e2016-01-26 13:41:30 -0700385 """
Stephen Warren10e50632016-01-15 11:15:24 -0700386
387 self._terminate_stream()
388 self.blocks.append(marker)
Stephen Warrene3f2a502016-02-03 16:46:34 -0700389 if not anchor:
390 self.anchor += 1
391 anchor = str(self.anchor)
Stephen Warren3deb8962016-01-26 13:41:31 -0700392 blk_path = '/'.join(self.blocks)
Stephen Warrene3f2a502016-02-03 16:46:34 -0700393 self.f.write('<div class="section block" id="' + anchor + '">\n')
394 self.f.write('<div class="section-header block-header">Section: ' +
395 blk_path + '</div>\n')
396 self.f.write('<div class="section-content block-content">\n')
397
398 return anchor
Stephen Warren10e50632016-01-15 11:15:24 -0700399
400 def end_section(self, marker):
Stephen Warren75e731e2016-01-26 13:41:30 -0700401 """Terminate the current nested section in the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700402
403 This function validates proper nesting of start_section() and
404 end_section() calls. If a mismatch is found, an exception is raised.
405
406 Args:
407 marker: The name of the section that is ending.
408
409 Returns:
410 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700411 """
Stephen Warren10e50632016-01-15 11:15:24 -0700412
413 if (not self.blocks) or (marker != self.blocks[-1]):
Stephen Warren3deb8962016-01-26 13:41:31 -0700414 raise Exception('Block nesting mismatch: "%s" "%s"' %
415 (marker, '/'.join(self.blocks)))
Stephen Warren10e50632016-01-15 11:15:24 -0700416 self._terminate_stream()
Stephen Warren3deb8962016-01-26 13:41:31 -0700417 blk_path = '/'.join(self.blocks)
Stephen Warrene3f2a502016-02-03 16:46:34 -0700418 self.f.write('<div class="section-trailer block-trailer">' +
419 'End section: ' + blk_path + '</div>\n')
420 self.f.write('</div>\n')
Stephen Warren3deb8962016-01-26 13:41:31 -0700421 self.f.write('</div>\n')
Stephen Warren10e50632016-01-15 11:15:24 -0700422 self.blocks.pop()
423
Stephen Warrene3f2a502016-02-03 16:46:34 -0700424 def section(self, marker, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700425 """Create a temporary section in the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700426
427 This function creates a context manager for Python's "with" statement,
428 which allows a certain portion of test code to be logged to a separate
429 section of the log file.
430
431 Usage:
432 with log.section("somename"):
433 some test code
434
435 Args:
436 marker: The name of the nested section.
Stephen Warrene3f2a502016-02-03 16:46:34 -0700437 anchor: The anchor value to pass to start_section().
Stephen Warren10e50632016-01-15 11:15:24 -0700438
439 Returns:
440 A context manager object.
Stephen Warren75e731e2016-01-26 13:41:30 -0700441 """
Stephen Warren10e50632016-01-15 11:15:24 -0700442
Stephen Warrene3f2a502016-02-03 16:46:34 -0700443 return SectionCtxMgr(self, marker, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700444
445 def error(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700446 """Write an error note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700447
448 Args:
449 msg: A message describing the error.
450
451 Returns:
452 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700453 """
Stephen Warren10e50632016-01-15 11:15:24 -0700454
455 self._note("error", msg)
456
457 def warning(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700458 """Write an warning note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700459
460 Args:
461 msg: A message describing the warning.
462
463 Returns:
464 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700465 """
Stephen Warren10e50632016-01-15 11:15:24 -0700466
467 self._note("warning", msg)
468
469 def info(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700470 """Write an informational note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700471
472 Args:
473 msg: An informational message.
474
475 Returns:
476 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700477 """
Stephen Warren10e50632016-01-15 11:15:24 -0700478
479 self._note("info", msg)
480
481 def action(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700482 """Write an action note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700483
484 Args:
485 msg: A message describing the action that is being logged.
486
487 Returns:
488 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700489 """
Stephen Warren10e50632016-01-15 11:15:24 -0700490
491 self._note("action", msg)
492
Stephen Warrene3f2a502016-02-03 16:46:34 -0700493 def status_pass(self, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700494 """Write a note to the log file describing test(s) which passed.
Stephen Warren10e50632016-01-15 11:15:24 -0700495
496 Args:
Stephen Warren25b05242016-01-27 23:57:51 -0700497 msg: A message describing the passed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700498 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700499
500 Returns:
501 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700502 """
Stephen Warren10e50632016-01-15 11:15:24 -0700503
Stephen Warrene3f2a502016-02-03 16:46:34 -0700504 self._note("status-pass", msg, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700505
Stephen Warrene3f2a502016-02-03 16:46:34 -0700506 def status_skipped(self, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700507 """Write a note to the log file describing skipped test(s).
Stephen Warren10e50632016-01-15 11:15:24 -0700508
509 Args:
Stephen Warren25b05242016-01-27 23:57:51 -0700510 msg: A message describing the skipped test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700511 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700512
513 Returns:
514 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700515 """
Stephen Warren10e50632016-01-15 11:15:24 -0700516
Stephen Warrene3f2a502016-02-03 16:46:34 -0700517 self._note("status-skipped", msg, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700518
Stephen Warrene3f2a502016-02-03 16:46:34 -0700519 def status_xfail(self, msg, anchor=None):
Stephen Warren25b05242016-01-27 23:57:51 -0700520 """Write a note to the log file describing xfailed test(s).
521
522 Args:
523 msg: A message describing the xfailed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700524 anchor: Optional internal link target.
Stephen Warren25b05242016-01-27 23:57:51 -0700525
526 Returns:
527 Nothing.
528 """
529
Stephen Warrene3f2a502016-02-03 16:46:34 -0700530 self._note("status-xfail", msg, anchor)
Stephen Warren25b05242016-01-27 23:57:51 -0700531
Stephen Warrene3f2a502016-02-03 16:46:34 -0700532 def status_xpass(self, msg, anchor=None):
Stephen Warren25b05242016-01-27 23:57:51 -0700533 """Write a note to the log file describing xpassed test(s).
534
535 Args:
536 msg: A message describing the xpassed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700537 anchor: Optional internal link target.
Stephen Warren25b05242016-01-27 23:57:51 -0700538
539 Returns:
540 Nothing.
541 """
542
Stephen Warrene3f2a502016-02-03 16:46:34 -0700543 self._note("status-xpass", msg, anchor)
Stephen Warren25b05242016-01-27 23:57:51 -0700544
Stephen Warrene3f2a502016-02-03 16:46:34 -0700545 def status_fail(self, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700546 """Write a note to the log file describing failed test(s).
Stephen Warren10e50632016-01-15 11:15:24 -0700547
548 Args:
Stephen Warren25b05242016-01-27 23:57:51 -0700549 msg: A message describing the failed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700550 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700551
552 Returns:
553 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700554 """
Stephen Warren10e50632016-01-15 11:15:24 -0700555
Stephen Warrene3f2a502016-02-03 16:46:34 -0700556 self._note("status-fail", msg, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700557
558 def get_stream(self, name, chained_file=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700559 """Create an object to log a single stream's data into the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700560
561 This creates a "file-like" object that can be written to in order to
562 write a single stream's data to the log file. The implementation will
563 handle any required interleaving of data (from multiple streams) in
564 the log, in a way that makes it obvious which stream each bit of data
565 came from.
566
567 Args:
568 name: The name of the stream.
569 chained_file: The file-like object to which all stream data should
570 be logged to in addition to this log. Can be None.
571
572 Returns:
573 A file-like object.
Stephen Warren75e731e2016-01-26 13:41:30 -0700574 """
Stephen Warren10e50632016-01-15 11:15:24 -0700575
576 return LogfileStream(self, name, chained_file)
577
578 def get_runner(self, name, chained_file=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700579 """Create an object that executes processes and logs their output.
Stephen Warren10e50632016-01-15 11:15:24 -0700580
581 Args:
582 name: The name of this sub-process.
583 chained_file: The file-like object to which all stream data should
584 be logged to in addition to logfile. Can be None.
585
586 Returns:
587 A RunAndLog object.
Stephen Warren75e731e2016-01-26 13:41:30 -0700588 """
Stephen Warren10e50632016-01-15 11:15:24 -0700589
590 return RunAndLog(self, name, chained_file)
591
592 def write(self, stream, data, implicit=False):
Stephen Warren75e731e2016-01-26 13:41:30 -0700593 """Write stream data into the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700594
595 This function should only be used by instances of LogfileStream or
596 RunAndLog.
597
598 Args:
599 stream: The stream whose data is being logged.
600 data: The data to log.
601 implicit: Boolean indicating whether data actually appeared in the
602 stream, or was implicitly generated. A valid use-case is to
603 repeat a shell prompt at the start of each separate log
604 section, which makes the log sections more readable in
605 isolation.
606
607 Returns:
608 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700609 """
Stephen Warren10e50632016-01-15 11:15:24 -0700610
611 if stream != self.last_stream:
612 self._terminate_stream()
Stephen Warrene3f2a502016-02-03 16:46:34 -0700613 self.f.write('<div class="stream block">\n')
614 self.f.write('<div class="stream-header block-header">Stream: ' +
615 stream.name + '</div>\n')
616 self.f.write('<div class="stream-content block-content">\n')
Stephen Warren3deb8962016-01-26 13:41:31 -0700617 self.f.write('<pre>')
Stephen Warren10e50632016-01-15 11:15:24 -0700618 if implicit:
Stephen Warren3deb8962016-01-26 13:41:31 -0700619 self.f.write('<span class="implicit">')
Stephen Warren10e50632016-01-15 11:15:24 -0700620 self.f.write(self._escape(data))
621 if implicit:
Stephen Warren3deb8962016-01-26 13:41:31 -0700622 self.f.write('</span>')
Stephen Warren10e50632016-01-15 11:15:24 -0700623 self.last_stream = stream
624
625 def flush(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700626 """Flush the log stream, to ensure correct log interleaving.
Stephen Warren10e50632016-01-15 11:15:24 -0700627
628 Args:
629 None.
630
631 Returns:
632 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700633 """
Stephen Warren10e50632016-01-15 11:15:24 -0700634
635 self.f.flush()