blob: 5bc1bc49d4de2d8f9433507f42bcdfd33a3916a1 [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
Simon Glass95adb802016-07-31 17:35:03 -0600105 self.exit_status = None
Stephen Warren10e50632016-01-15 11:15:24 -0700106
107 def close(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700108 """Clean up any resources managed by this object."""
Stephen Warren10e50632016-01-15 11:15:24 -0700109 pass
110
Stephen Warren118e37e2016-01-22 12:30:11 -0700111 def run(self, cmd, cwd=None, ignore_errors=False):
Stephen Warren75e731e2016-01-26 13:41:30 -0700112 """Run a command as a sub-process, and log the results.
Stephen Warren10e50632016-01-15 11:15:24 -0700113
Simon Glass4134caf2016-07-03 09:40:38 -0600114 The output is available at self.output which can be useful if there is
115 an exception.
116
Stephen Warren10e50632016-01-15 11:15:24 -0700117 Args:
118 cmd: The command to execute.
119 cwd: The directory to run the command in. Can be None to use the
120 current directory.
Stephen Warren118e37e2016-01-22 12:30:11 -0700121 ignore_errors: Indicate whether to ignore errors. If True, the
122 function will simply return if the command cannot be executed
123 or exits with an error code, otherwise an exception will be
124 raised if such problems occur.
Stephen Warren10e50632016-01-15 11:15:24 -0700125
126 Returns:
Simon Glass9f3c9e92016-07-03 09:40:37 -0600127 The output as a string.
Stephen Warren75e731e2016-01-26 13:41:30 -0700128 """
Stephen Warren10e50632016-01-15 11:15:24 -0700129
Stephen Warren3deb8962016-01-26 13:41:31 -0700130 msg = '+' + ' '.join(cmd) + '\n'
Stephen Warren10e50632016-01-15 11:15:24 -0700131 if self.chained_file:
132 self.chained_file.write(msg)
133 self.logfile.write(self, msg)
134
135 try:
136 p = subprocess.Popen(cmd, cwd=cwd,
137 stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
138 (stdout, stderr) = p.communicate()
139 output = ''
140 if stdout:
141 if stderr:
142 output += 'stdout:\n'
143 output += stdout
144 if stderr:
145 if stdout:
146 output += 'stderr:\n'
147 output += stderr
148 exit_status = p.returncode
149 exception = None
150 except subprocess.CalledProcessError as cpe:
151 output = cpe.output
152 exit_status = cpe.returncode
153 exception = cpe
154 except Exception as e:
155 output = ''
156 exit_status = 0
157 exception = e
158 if output and not output.endswith('\n'):
159 output += '\n'
Stephen Warren118e37e2016-01-22 12:30:11 -0700160 if exit_status and not exception and not ignore_errors:
Stephen Warren10e50632016-01-15 11:15:24 -0700161 exception = Exception('Exit code: ' + str(exit_status))
162 if exception:
163 output += str(exception) + '\n'
164 self.logfile.write(self, output)
165 if self.chained_file:
166 self.chained_file.write(output)
Simon Glass4134caf2016-07-03 09:40:38 -0600167
168 # Store the output so it can be accessed if we raise an exception.
169 self.output = output
Simon Glass95adb802016-07-31 17:35:03 -0600170 self.exit_status = exit_status
Stephen Warren10e50632016-01-15 11:15:24 -0700171 if exception:
172 raise exception
Simon Glass9f3c9e92016-07-03 09:40:37 -0600173 return output
Stephen Warren10e50632016-01-15 11:15:24 -0700174
175class SectionCtxMgr(object):
Stephen Warren75e731e2016-01-26 13:41:30 -0700176 """A context manager for Python's "with" statement, which allows a certain
Stephen Warren10e50632016-01-15 11:15:24 -0700177 portion of test code to be logged to a separate section of the log file.
178 Objects of this type should be created by factory functions in the Logfile
Stephen Warren75e731e2016-01-26 13:41:30 -0700179 class rather than directly."""
Stephen Warren10e50632016-01-15 11:15:24 -0700180
Stephen Warrene3f2a502016-02-03 16:46:34 -0700181 def __init__(self, log, marker, anchor):
Stephen Warren75e731e2016-01-26 13:41:30 -0700182 """Initialize a new object.
Stephen Warren10e50632016-01-15 11:15:24 -0700183
184 Args:
185 log: The Logfile object to log to.
186 marker: The name of the nested log section.
Stephen Warrene3f2a502016-02-03 16:46:34 -0700187 anchor: The anchor value to pass to start_section().
Stephen Warren10e50632016-01-15 11:15:24 -0700188
189 Returns:
190 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700191 """
Stephen Warren10e50632016-01-15 11:15:24 -0700192
193 self.log = log
194 self.marker = marker
Stephen Warrene3f2a502016-02-03 16:46:34 -0700195 self.anchor = anchor
Stephen Warren10e50632016-01-15 11:15:24 -0700196
197 def __enter__(self):
Stephen Warrene3f2a502016-02-03 16:46:34 -0700198 self.anchor = self.log.start_section(self.marker, self.anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700199
200 def __exit__(self, extype, value, traceback):
201 self.log.end_section(self.marker)
202
203class Logfile(object):
Stephen Warren75e731e2016-01-26 13:41:30 -0700204 """Generates an HTML-formatted log file containing multiple streams of
205 data, each represented in a well-delineated/-structured fashion."""
Stephen Warren10e50632016-01-15 11:15:24 -0700206
207 def __init__(self, fn):
Stephen Warren75e731e2016-01-26 13:41:30 -0700208 """Initialize a new object.
Stephen Warren10e50632016-01-15 11:15:24 -0700209
210 Args:
211 fn: The filename to write to.
212
213 Returns:
214 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700215 """
Stephen Warren10e50632016-01-15 11:15:24 -0700216
Stephen Warren3deb8962016-01-26 13:41:31 -0700217 self.f = open(fn, 'wt')
Stephen Warren10e50632016-01-15 11:15:24 -0700218 self.last_stream = None
219 self.blocks = []
220 self.cur_evt = 1
Stephen Warrene3f2a502016-02-03 16:46:34 -0700221 self.anchor = 0
222
Stephen Warren3deb8962016-01-26 13:41:31 -0700223 shutil.copy(mod_dir + '/multiplexed_log.css', os.path.dirname(fn))
224 self.f.write('''\
Stephen Warren10e50632016-01-15 11:15:24 -0700225<html>
226<head>
227<link rel="stylesheet" type="text/css" href="multiplexed_log.css">
Stephen Warrene3f2a502016-02-03 16:46:34 -0700228<script src="http://code.jquery.com/jquery.min.js"></script>
229<script>
230$(document).ready(function () {
231 // Copy status report HTML to start of log for easy access
232 sts = $(".block#status_report")[0].outerHTML;
233 $("tt").prepend(sts);
234
235 // Add expand/contract buttons to all block headers
236 btns = "<span class=\\\"block-expand hidden\\\">[+] </span>" +
237 "<span class=\\\"block-contract\\\">[-] </span>";
238 $(".block-header").prepend(btns);
239
240 // Pre-contract all blocks which passed, leaving only problem cases
241 // expanded, to highlight issues the user should look at.
242 // Only top-level blocks (sections) should have any status
243 passed_bcs = $(".block-content:has(.status-pass)");
244 // Some blocks might have multiple status entries (e.g. the status
245 // report), so take care not to hide blocks with partial success.
246 passed_bcs = passed_bcs.not(":has(.status-fail)");
247 passed_bcs = passed_bcs.not(":has(.status-xfail)");
248 passed_bcs = passed_bcs.not(":has(.status-xpass)");
249 passed_bcs = passed_bcs.not(":has(.status-skipped)");
250 // Hide the passed blocks
251 passed_bcs.addClass("hidden");
252 // Flip the expand/contract button hiding for those blocks.
253 bhs = passed_bcs.parent().children(".block-header")
254 bhs.children(".block-expand").removeClass("hidden");
255 bhs.children(".block-contract").addClass("hidden");
256
257 // Add click handler to block headers.
258 // The handler expands/contracts the block.
259 $(".block-header").on("click", function (e) {
260 var header = $(this);
261 var content = header.next(".block-content");
262 var expanded = !content.hasClass("hidden");
263 if (expanded) {
264 content.addClass("hidden");
265 header.children(".block-expand").first().removeClass("hidden");
266 header.children(".block-contract").first().addClass("hidden");
267 } else {
268 header.children(".block-contract").first().removeClass("hidden");
269 header.children(".block-expand").first().addClass("hidden");
270 content.removeClass("hidden");
271 }
272 });
273
274 // When clicking on a link, expand the target block
275 $("a").on("click", function (e) {
276 var block = $($(this).attr("href"));
277 var header = block.children(".block-header");
278 var content = block.children(".block-content").first();
279 header.children(".block-contract").first().removeClass("hidden");
280 header.children(".block-expand").first().addClass("hidden");
281 content.removeClass("hidden");
282 });
283});
284</script>
Stephen Warren10e50632016-01-15 11:15:24 -0700285</head>
286<body>
287<tt>
Stephen Warren3deb8962016-01-26 13:41:31 -0700288''')
Stephen Warren10e50632016-01-15 11:15:24 -0700289
290 def close(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700291 """Close the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700292
293 After calling this function, no more data may be written to the log.
294
295 Args:
296 None.
297
298 Returns:
299 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700300 """
Stephen Warren10e50632016-01-15 11:15:24 -0700301
Stephen Warren3deb8962016-01-26 13:41:31 -0700302 self.f.write('''\
Stephen Warren10e50632016-01-15 11:15:24 -0700303</tt>
304</body>
305</html>
Stephen Warren3deb8962016-01-26 13:41:31 -0700306''')
Stephen Warren10e50632016-01-15 11:15:24 -0700307 self.f.close()
308
309 # The set of characters that should be represented as hexadecimal codes in
310 # the log file.
Stephen Warren3deb8962016-01-26 13:41:31 -0700311 _nonprint = ('%' + ''.join(chr(c) for c in range(0, 32) if c not in (9, 10)) +
312 ''.join(chr(c) for c in range(127, 256)))
Stephen Warren10e50632016-01-15 11:15:24 -0700313
314 def _escape(self, data):
Stephen Warren75e731e2016-01-26 13:41:30 -0700315 """Render data format suitable for inclusion in an HTML document.
Stephen Warren10e50632016-01-15 11:15:24 -0700316
317 This includes HTML-escaping certain characters, and translating
318 control characters to a hexadecimal representation.
319
320 Args:
321 data: The raw string data to be escaped.
322
323 Returns:
324 An escaped version of the data.
Stephen Warren75e731e2016-01-26 13:41:30 -0700325 """
Stephen Warren10e50632016-01-15 11:15:24 -0700326
Stephen Warren3deb8962016-01-26 13:41:31 -0700327 data = data.replace(chr(13), '')
328 data = ''.join((c in self._nonprint) and ('%%%02x' % ord(c)) or
Stephen Warren10e50632016-01-15 11:15:24 -0700329 c for c in data)
330 data = cgi.escape(data)
331 return data
332
333 def _terminate_stream(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700334 """Write HTML to the log file to terminate the current stream's data.
Stephen Warren10e50632016-01-15 11:15:24 -0700335
336 Args:
337 None.
338
339 Returns:
340 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700341 """
Stephen Warren10e50632016-01-15 11:15:24 -0700342
343 self.cur_evt += 1
344 if not self.last_stream:
345 return
Stephen Warren3deb8962016-01-26 13:41:31 -0700346 self.f.write('</pre>\n')
Stephen Warrene3f2a502016-02-03 16:46:34 -0700347 self.f.write('<div class="stream-trailer block-trailer">End stream: ' +
Stephen Warren3deb8962016-01-26 13:41:31 -0700348 self.last_stream.name + '</div>\n')
349 self.f.write('</div>\n')
Stephen Warrene3f2a502016-02-03 16:46:34 -0700350 self.f.write('</div>\n')
Stephen Warren10e50632016-01-15 11:15:24 -0700351 self.last_stream = None
352
Stephen Warrene3f2a502016-02-03 16:46:34 -0700353 def _note(self, note_type, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700354 """Write a note or one-off message to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700355
356 Args:
357 note_type: The type of note. This must be a value supported by the
358 accompanying multiplexed_log.css.
359 msg: The note/message to log.
Stephen Warrene3f2a502016-02-03 16:46:34 -0700360 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700361
362 Returns:
363 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700364 """
Stephen Warren10e50632016-01-15 11:15:24 -0700365
366 self._terminate_stream()
Stephen Warrene3f2a502016-02-03 16:46:34 -0700367 self.f.write('<div class="' + note_type + '">\n')
Stephen Warrene3f2a502016-02-03 16:46:34 -0700368 self.f.write('<pre>')
Stephen Warrene0d2aee2017-09-18 11:50:47 -0600369 if anchor:
370 self.f.write('<a href="#%s">' % anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700371 self.f.write(self._escape(msg))
Stephen Warrene3f2a502016-02-03 16:46:34 -0700372 if anchor:
Stephen Warrene0d2aee2017-09-18 11:50:47 -0600373 self.f.write('</a>')
374 self.f.write('\n</pre>\n')
Stephen Warrene3f2a502016-02-03 16:46:34 -0700375 self.f.write('</div>\n')
Stephen Warren10e50632016-01-15 11:15:24 -0700376
Stephen Warrene3f2a502016-02-03 16:46:34 -0700377 def start_section(self, marker, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700378 """Begin a new nested section in the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700379
380 Args:
381 marker: The name of the section that is starting.
Stephen Warrene3f2a502016-02-03 16:46:34 -0700382 anchor: The value to use for the anchor. If None, a unique value
383 will be calculated and used
Stephen Warren10e50632016-01-15 11:15:24 -0700384
385 Returns:
Stephen Warrene3f2a502016-02-03 16:46:34 -0700386 Name of the HTML anchor emitted before section.
Stephen Warren75e731e2016-01-26 13:41:30 -0700387 """
Stephen Warren10e50632016-01-15 11:15:24 -0700388
389 self._terminate_stream()
390 self.blocks.append(marker)
Stephen Warrene3f2a502016-02-03 16:46:34 -0700391 if not anchor:
392 self.anchor += 1
393 anchor = str(self.anchor)
Stephen Warren3deb8962016-01-26 13:41:31 -0700394 blk_path = '/'.join(self.blocks)
Stephen Warrene3f2a502016-02-03 16:46:34 -0700395 self.f.write('<div class="section block" id="' + anchor + '">\n')
396 self.f.write('<div class="section-header block-header">Section: ' +
397 blk_path + '</div>\n')
398 self.f.write('<div class="section-content block-content">\n')
399
400 return anchor
Stephen Warren10e50632016-01-15 11:15:24 -0700401
402 def end_section(self, marker):
Stephen Warren75e731e2016-01-26 13:41:30 -0700403 """Terminate the current nested section in the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700404
405 This function validates proper nesting of start_section() and
406 end_section() calls. If a mismatch is found, an exception is raised.
407
408 Args:
409 marker: The name of the section that is ending.
410
411 Returns:
412 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700413 """
Stephen Warren10e50632016-01-15 11:15:24 -0700414
415 if (not self.blocks) or (marker != self.blocks[-1]):
Stephen Warren3deb8962016-01-26 13:41:31 -0700416 raise Exception('Block nesting mismatch: "%s" "%s"' %
417 (marker, '/'.join(self.blocks)))
Stephen Warren10e50632016-01-15 11:15:24 -0700418 self._terminate_stream()
Stephen Warren3deb8962016-01-26 13:41:31 -0700419 blk_path = '/'.join(self.blocks)
Stephen Warrene3f2a502016-02-03 16:46:34 -0700420 self.f.write('<div class="section-trailer block-trailer">' +
421 'End section: ' + blk_path + '</div>\n')
422 self.f.write('</div>\n')
Stephen Warren3deb8962016-01-26 13:41:31 -0700423 self.f.write('</div>\n')
Stephen Warren10e50632016-01-15 11:15:24 -0700424 self.blocks.pop()
425
Stephen Warrene3f2a502016-02-03 16:46:34 -0700426 def section(self, marker, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700427 """Create a temporary section in the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700428
429 This function creates a context manager for Python's "with" statement,
430 which allows a certain portion of test code to be logged to a separate
431 section of the log file.
432
433 Usage:
434 with log.section("somename"):
435 some test code
436
437 Args:
438 marker: The name of the nested section.
Stephen Warrene3f2a502016-02-03 16:46:34 -0700439 anchor: The anchor value to pass to start_section().
Stephen Warren10e50632016-01-15 11:15:24 -0700440
441 Returns:
442 A context manager object.
Stephen Warren75e731e2016-01-26 13:41:30 -0700443 """
Stephen Warren10e50632016-01-15 11:15:24 -0700444
Stephen Warrene3f2a502016-02-03 16:46:34 -0700445 return SectionCtxMgr(self, marker, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700446
447 def error(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700448 """Write an error note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700449
450 Args:
451 msg: A message describing the error.
452
453 Returns:
454 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700455 """
Stephen Warren10e50632016-01-15 11:15:24 -0700456
457 self._note("error", msg)
458
459 def warning(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700460 """Write an warning note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700461
462 Args:
463 msg: A message describing the warning.
464
465 Returns:
466 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700467 """
Stephen Warren10e50632016-01-15 11:15:24 -0700468
469 self._note("warning", msg)
470
471 def info(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700472 """Write an informational note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700473
474 Args:
475 msg: An informational message.
476
477 Returns:
478 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700479 """
Stephen Warren10e50632016-01-15 11:15:24 -0700480
481 self._note("info", msg)
482
483 def action(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700484 """Write an action note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700485
486 Args:
487 msg: A message describing the action that is being logged.
488
489 Returns:
490 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700491 """
Stephen Warren10e50632016-01-15 11:15:24 -0700492
493 self._note("action", msg)
494
Stephen Warrene3f2a502016-02-03 16:46:34 -0700495 def status_pass(self, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700496 """Write a note to the log file describing test(s) which passed.
Stephen Warren10e50632016-01-15 11:15:24 -0700497
498 Args:
Stephen Warren25b05242016-01-27 23:57:51 -0700499 msg: A message describing the passed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700500 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700501
502 Returns:
503 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700504 """
Stephen Warren10e50632016-01-15 11:15:24 -0700505
Stephen Warrene3f2a502016-02-03 16:46:34 -0700506 self._note("status-pass", msg, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700507
Stephen Warrene3f2a502016-02-03 16:46:34 -0700508 def status_skipped(self, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700509 """Write a note to the log file describing skipped test(s).
Stephen Warren10e50632016-01-15 11:15:24 -0700510
511 Args:
Stephen Warren25b05242016-01-27 23:57:51 -0700512 msg: A message describing the skipped test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700513 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700514
515 Returns:
516 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700517 """
Stephen Warren10e50632016-01-15 11:15:24 -0700518
Stephen Warrene3f2a502016-02-03 16:46:34 -0700519 self._note("status-skipped", msg, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700520
Stephen Warrene3f2a502016-02-03 16:46:34 -0700521 def status_xfail(self, msg, anchor=None):
Stephen Warren25b05242016-01-27 23:57:51 -0700522 """Write a note to the log file describing xfailed test(s).
523
524 Args:
525 msg: A message describing the xfailed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700526 anchor: Optional internal link target.
Stephen Warren25b05242016-01-27 23:57:51 -0700527
528 Returns:
529 Nothing.
530 """
531
Stephen Warrene3f2a502016-02-03 16:46:34 -0700532 self._note("status-xfail", msg, anchor)
Stephen Warren25b05242016-01-27 23:57:51 -0700533
Stephen Warrene3f2a502016-02-03 16:46:34 -0700534 def status_xpass(self, msg, anchor=None):
Stephen Warren25b05242016-01-27 23:57:51 -0700535 """Write a note to the log file describing xpassed test(s).
536
537 Args:
538 msg: A message describing the xpassed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700539 anchor: Optional internal link target.
Stephen Warren25b05242016-01-27 23:57:51 -0700540
541 Returns:
542 Nothing.
543 """
544
Stephen Warrene3f2a502016-02-03 16:46:34 -0700545 self._note("status-xpass", msg, anchor)
Stephen Warren25b05242016-01-27 23:57:51 -0700546
Stephen Warrene3f2a502016-02-03 16:46:34 -0700547 def status_fail(self, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700548 """Write a note to the log file describing failed test(s).
Stephen Warren10e50632016-01-15 11:15:24 -0700549
550 Args:
Stephen Warren25b05242016-01-27 23:57:51 -0700551 msg: A message describing the failed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700552 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700553
554 Returns:
555 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700556 """
Stephen Warren10e50632016-01-15 11:15:24 -0700557
Stephen Warrene3f2a502016-02-03 16:46:34 -0700558 self._note("status-fail", msg, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700559
560 def get_stream(self, name, chained_file=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700561 """Create an object to log a single stream's data into the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700562
563 This creates a "file-like" object that can be written to in order to
564 write a single stream's data to the log file. The implementation will
565 handle any required interleaving of data (from multiple streams) in
566 the log, in a way that makes it obvious which stream each bit of data
567 came from.
568
569 Args:
570 name: The name of the stream.
571 chained_file: The file-like object to which all stream data should
572 be logged to in addition to this log. Can be None.
573
574 Returns:
575 A file-like object.
Stephen Warren75e731e2016-01-26 13:41:30 -0700576 """
Stephen Warren10e50632016-01-15 11:15:24 -0700577
578 return LogfileStream(self, name, chained_file)
579
580 def get_runner(self, name, chained_file=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700581 """Create an object that executes processes and logs their output.
Stephen Warren10e50632016-01-15 11:15:24 -0700582
583 Args:
584 name: The name of this sub-process.
585 chained_file: The file-like object to which all stream data should
586 be logged to in addition to logfile. Can be None.
587
588 Returns:
589 A RunAndLog object.
Stephen Warren75e731e2016-01-26 13:41:30 -0700590 """
Stephen Warren10e50632016-01-15 11:15:24 -0700591
592 return RunAndLog(self, name, chained_file)
593
594 def write(self, stream, data, implicit=False):
Stephen Warren75e731e2016-01-26 13:41:30 -0700595 """Write stream data into the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700596
597 This function should only be used by instances of LogfileStream or
598 RunAndLog.
599
600 Args:
601 stream: The stream whose data is being logged.
602 data: The data to log.
603 implicit: Boolean indicating whether data actually appeared in the
604 stream, or was implicitly generated. A valid use-case is to
605 repeat a shell prompt at the start of each separate log
606 section, which makes the log sections more readable in
607 isolation.
608
609 Returns:
610 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700611 """
Stephen Warren10e50632016-01-15 11:15:24 -0700612
613 if stream != self.last_stream:
614 self._terminate_stream()
Stephen Warrene3f2a502016-02-03 16:46:34 -0700615 self.f.write('<div class="stream block">\n')
616 self.f.write('<div class="stream-header block-header">Stream: ' +
617 stream.name + '</div>\n')
618 self.f.write('<div class="stream-content block-content">\n')
Stephen Warren3deb8962016-01-26 13:41:31 -0700619 self.f.write('<pre>')
Stephen Warren10e50632016-01-15 11:15:24 -0700620 if implicit:
Stephen Warren3deb8962016-01-26 13:41:31 -0700621 self.f.write('<span class="implicit">')
Stephen Warren10e50632016-01-15 11:15:24 -0700622 self.f.write(self._escape(data))
623 if implicit:
Stephen Warren3deb8962016-01-26 13:41:31 -0700624 self.f.write('</span>')
Stephen Warren10e50632016-01-15 11:15:24 -0700625 self.last_stream = stream
626
627 def flush(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700628 """Flush the log stream, to ensure correct log interleaving.
Stephen Warren10e50632016-01-15 11:15:24 -0700629
630 Args:
631 None.
632
633 Returns:
634 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700635 """
Stephen Warren10e50632016-01-15 11:15:24 -0700636
637 self.f.flush()