blob: 02c44df883aedc919e479f7ef347bf6a68d08513 [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:
Simon Glass3b8d9d92016-07-03 09:40:37 -0600122 The output as a string.
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
Simon Glass3b8d9d92016-07-03 09:40:37 -0600164 return output
Stephen Warrend2015062016-01-15 11:15:24 -0700165
166class SectionCtxMgr(object):
Stephen Warrene8debf32016-01-26 13:41:30 -0700167 """A context manager for Python's "with" statement, which allows a certain
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -0700170 class rather than directly."""
Stephen Warrend2015062016-01-15 11:15:24 -0700171
Stephen Warren83357fd2016-02-03 16:46:34 -0700172 def __init__(self, log, marker, anchor):
Stephen Warrene8debf32016-01-26 13:41:30 -0700173 """Initialize a new object.
Stephen Warrend2015062016-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 Warren83357fd2016-02-03 16:46:34 -0700178 anchor: The anchor value to pass to start_section().
Stephen Warrend2015062016-01-15 11:15:24 -0700179
180 Returns:
181 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700182 """
Stephen Warrend2015062016-01-15 11:15:24 -0700183
184 self.log = log
185 self.marker = marker
Stephen Warren83357fd2016-02-03 16:46:34 -0700186 self.anchor = anchor
Stephen Warrend2015062016-01-15 11:15:24 -0700187
188 def __enter__(self):
Stephen Warren83357fd2016-02-03 16:46:34 -0700189 self.anchor = self.log.start_section(self.marker, self.anchor)
Stephen Warrend2015062016-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 Warrene8debf32016-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 Warrend2015062016-01-15 11:15:24 -0700197
198 def __init__(self, fn):
Stephen Warrene8debf32016-01-26 13:41:30 -0700199 """Initialize a new object.
Stephen Warrend2015062016-01-15 11:15:24 -0700200
201 Args:
202 fn: The filename to write to.
203
204 Returns:
205 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700206 """
Stephen Warrend2015062016-01-15 11:15:24 -0700207
Stephen Warrena2ec5602016-01-26 13:41:31 -0700208 self.f = open(fn, 'wt')
Stephen Warrend2015062016-01-15 11:15:24 -0700209 self.last_stream = None
210 self.blocks = []
211 self.cur_evt = 1
Stephen Warren83357fd2016-02-03 16:46:34 -0700212 self.anchor = 0
213
Stephen Warrena2ec5602016-01-26 13:41:31 -0700214 shutil.copy(mod_dir + '/multiplexed_log.css', os.path.dirname(fn))
215 self.f.write('''\
Stephen Warrend2015062016-01-15 11:15:24 -0700216<html>
217<head>
218<link rel="stylesheet" type="text/css" href="multiplexed_log.css">
Stephen Warren83357fd2016-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 Warrend2015062016-01-15 11:15:24 -0700276</head>
277<body>
278<tt>
Stephen Warrena2ec5602016-01-26 13:41:31 -0700279''')
Stephen Warrend2015062016-01-15 11:15:24 -0700280
281 def close(self):
Stephen Warrene8debf32016-01-26 13:41:30 -0700282 """Close the log file.
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -0700291 """
Stephen Warrend2015062016-01-15 11:15:24 -0700292
Stephen Warrena2ec5602016-01-26 13:41:31 -0700293 self.f.write('''\
Stephen Warrend2015062016-01-15 11:15:24 -0700294</tt>
295</body>
296</html>
Stephen Warrena2ec5602016-01-26 13:41:31 -0700297''')
Stephen Warrend2015062016-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 Warrena2ec5602016-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 Warrend2015062016-01-15 11:15:24 -0700304
305 def _escape(self, data):
Stephen Warrene8debf32016-01-26 13:41:30 -0700306 """Render data format suitable for inclusion in an HTML document.
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -0700316 """
Stephen Warrend2015062016-01-15 11:15:24 -0700317
Stephen Warrena2ec5602016-01-26 13:41:31 -0700318 data = data.replace(chr(13), '')
319 data = ''.join((c in self._nonprint) and ('%%%02x' % ord(c)) or
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -0700325 """Write HTML to the log file to terminate the current stream's data.
Stephen Warrend2015062016-01-15 11:15:24 -0700326
327 Args:
328 None.
329
330 Returns:
331 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700332 """
Stephen Warrend2015062016-01-15 11:15:24 -0700333
334 self.cur_evt += 1
335 if not self.last_stream:
336 return
Stephen Warrena2ec5602016-01-26 13:41:31 -0700337 self.f.write('</pre>\n')
Stephen Warren83357fd2016-02-03 16:46:34 -0700338 self.f.write('<div class="stream-trailer block-trailer">End stream: ' +
Stephen Warrena2ec5602016-01-26 13:41:31 -0700339 self.last_stream.name + '</div>\n')
340 self.f.write('</div>\n')
Stephen Warren83357fd2016-02-03 16:46:34 -0700341 self.f.write('</div>\n')
Stephen Warrend2015062016-01-15 11:15:24 -0700342 self.last_stream = None
343
Stephen Warren83357fd2016-02-03 16:46:34 -0700344 def _note(self, note_type, msg, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700345 """Write a note or one-off message to the log file.
Stephen Warrend2015062016-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 Warren83357fd2016-02-03 16:46:34 -0700351 anchor: Optional internal link target.
Stephen Warrend2015062016-01-15 11:15:24 -0700352
353 Returns:
354 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700355 """
Stephen Warrend2015062016-01-15 11:15:24 -0700356
357 self._terminate_stream()
Stephen Warren83357fd2016-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 Warrend2015062016-01-15 11:15:24 -0700362 self.f.write(self._escape(msg))
Stephen Warren83357fd2016-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 Warrend2015062016-01-15 11:15:24 -0700367
Stephen Warren83357fd2016-02-03 16:46:34 -0700368 def start_section(self, marker, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700369 """Begin a new nested section in the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700370
371 Args:
372 marker: The name of the section that is starting.
Stephen Warren83357fd2016-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 Warrend2015062016-01-15 11:15:24 -0700375
376 Returns:
Stephen Warren83357fd2016-02-03 16:46:34 -0700377 Name of the HTML anchor emitted before section.
Stephen Warrene8debf32016-01-26 13:41:30 -0700378 """
Stephen Warrend2015062016-01-15 11:15:24 -0700379
380 self._terminate_stream()
381 self.blocks.append(marker)
Stephen Warren83357fd2016-02-03 16:46:34 -0700382 if not anchor:
383 self.anchor += 1
384 anchor = str(self.anchor)
Stephen Warrena2ec5602016-01-26 13:41:31 -0700385 blk_path = '/'.join(self.blocks)
Stephen Warren83357fd2016-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 Warrend2015062016-01-15 11:15:24 -0700392
393 def end_section(self, marker):
Stephen Warrene8debf32016-01-26 13:41:30 -0700394 """Terminate the current nested section in the log file.
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -0700404 """
Stephen Warrend2015062016-01-15 11:15:24 -0700405
406 if (not self.blocks) or (marker != self.blocks[-1]):
Stephen Warrena2ec5602016-01-26 13:41:31 -0700407 raise Exception('Block nesting mismatch: "%s" "%s"' %
408 (marker, '/'.join(self.blocks)))
Stephen Warrend2015062016-01-15 11:15:24 -0700409 self._terminate_stream()
Stephen Warrena2ec5602016-01-26 13:41:31 -0700410 blk_path = '/'.join(self.blocks)
Stephen Warren83357fd2016-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 Warrena2ec5602016-01-26 13:41:31 -0700414 self.f.write('</div>\n')
Stephen Warrend2015062016-01-15 11:15:24 -0700415 self.blocks.pop()
416
Stephen Warren83357fd2016-02-03 16:46:34 -0700417 def section(self, marker, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700418 """Create a temporary section in the log file.
Stephen Warrend2015062016-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 Warren83357fd2016-02-03 16:46:34 -0700430 anchor: The anchor value to pass to start_section().
Stephen Warrend2015062016-01-15 11:15:24 -0700431
432 Returns:
433 A context manager object.
Stephen Warrene8debf32016-01-26 13:41:30 -0700434 """
Stephen Warrend2015062016-01-15 11:15:24 -0700435
Stephen Warren83357fd2016-02-03 16:46:34 -0700436 return SectionCtxMgr(self, marker, anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700437
438 def error(self, msg):
Stephen Warrene8debf32016-01-26 13:41:30 -0700439 """Write an error note to the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700440
441 Args:
442 msg: A message describing the error.
443
444 Returns:
445 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700446 """
Stephen Warrend2015062016-01-15 11:15:24 -0700447
448 self._note("error", msg)
449
450 def warning(self, msg):
Stephen Warrene8debf32016-01-26 13:41:30 -0700451 """Write an warning note to the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700452
453 Args:
454 msg: A message describing the warning.
455
456 Returns:
457 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700458 """
Stephen Warrend2015062016-01-15 11:15:24 -0700459
460 self._note("warning", msg)
461
462 def info(self, msg):
Stephen Warrene8debf32016-01-26 13:41:30 -0700463 """Write an informational note to the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700464
465 Args:
466 msg: An informational message.
467
468 Returns:
469 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700470 """
Stephen Warrend2015062016-01-15 11:15:24 -0700471
472 self._note("info", msg)
473
474 def action(self, msg):
Stephen Warrene8debf32016-01-26 13:41:30 -0700475 """Write an action note to the log file.
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -0700482 """
Stephen Warrend2015062016-01-15 11:15:24 -0700483
484 self._note("action", msg)
485
Stephen Warren83357fd2016-02-03 16:46:34 -0700486 def status_pass(self, msg, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700487 """Write a note to the log file describing test(s) which passed.
Stephen Warrend2015062016-01-15 11:15:24 -0700488
489 Args:
Stephen Warren78b39cc2016-01-27 23:57:51 -0700490 msg: A message describing the passed test(s).
Stephen Warren83357fd2016-02-03 16:46:34 -0700491 anchor: Optional internal link target.
Stephen Warrend2015062016-01-15 11:15:24 -0700492
493 Returns:
494 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700495 """
Stephen Warrend2015062016-01-15 11:15:24 -0700496
Stephen Warren83357fd2016-02-03 16:46:34 -0700497 self._note("status-pass", msg, anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700498
Stephen Warren83357fd2016-02-03 16:46:34 -0700499 def status_skipped(self, msg, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700500 """Write a note to the log file describing skipped test(s).
Stephen Warrend2015062016-01-15 11:15:24 -0700501
502 Args:
Stephen Warren78b39cc2016-01-27 23:57:51 -0700503 msg: A message describing the skipped test(s).
Stephen Warren83357fd2016-02-03 16:46:34 -0700504 anchor: Optional internal link target.
Stephen Warrend2015062016-01-15 11:15:24 -0700505
506 Returns:
507 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700508 """
Stephen Warrend2015062016-01-15 11:15:24 -0700509
Stephen Warren83357fd2016-02-03 16:46:34 -0700510 self._note("status-skipped", msg, anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700511
Stephen Warren83357fd2016-02-03 16:46:34 -0700512 def status_xfail(self, msg, anchor=None):
Stephen Warren78b39cc2016-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 Warren83357fd2016-02-03 16:46:34 -0700517 anchor: Optional internal link target.
Stephen Warren78b39cc2016-01-27 23:57:51 -0700518
519 Returns:
520 Nothing.
521 """
522
Stephen Warren83357fd2016-02-03 16:46:34 -0700523 self._note("status-xfail", msg, anchor)
Stephen Warren78b39cc2016-01-27 23:57:51 -0700524
Stephen Warren83357fd2016-02-03 16:46:34 -0700525 def status_xpass(self, msg, anchor=None):
Stephen Warren78b39cc2016-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 Warren83357fd2016-02-03 16:46:34 -0700530 anchor: Optional internal link target.
Stephen Warren78b39cc2016-01-27 23:57:51 -0700531
532 Returns:
533 Nothing.
534 """
535
Stephen Warren83357fd2016-02-03 16:46:34 -0700536 self._note("status-xpass", msg, anchor)
Stephen Warren78b39cc2016-01-27 23:57:51 -0700537
Stephen Warren83357fd2016-02-03 16:46:34 -0700538 def status_fail(self, msg, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700539 """Write a note to the log file describing failed test(s).
Stephen Warrend2015062016-01-15 11:15:24 -0700540
541 Args:
Stephen Warren78b39cc2016-01-27 23:57:51 -0700542 msg: A message describing the failed test(s).
Stephen Warren83357fd2016-02-03 16:46:34 -0700543 anchor: Optional internal link target.
Stephen Warrend2015062016-01-15 11:15:24 -0700544
545 Returns:
546 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700547 """
Stephen Warrend2015062016-01-15 11:15:24 -0700548
Stephen Warren83357fd2016-02-03 16:46:34 -0700549 self._note("status-fail", msg, anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700550
551 def get_stream(self, name, chained_file=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700552 """Create an object to log a single stream's data into the log file.
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -0700567 """
Stephen Warrend2015062016-01-15 11:15:24 -0700568
569 return LogfileStream(self, name, chained_file)
570
571 def get_runner(self, name, chained_file=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700572 """Create an object that executes processes and logs their output.
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -0700581 """
Stephen Warrend2015062016-01-15 11:15:24 -0700582
583 return RunAndLog(self, name, chained_file)
584
585 def write(self, stream, data, implicit=False):
Stephen Warrene8debf32016-01-26 13:41:30 -0700586 """Write stream data into the log file.
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -0700602 """
Stephen Warrend2015062016-01-15 11:15:24 -0700603
604 if stream != self.last_stream:
605 self._terminate_stream()
Stephen Warren83357fd2016-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 Warrena2ec5602016-01-26 13:41:31 -0700610 self.f.write('<pre>')
Stephen Warrend2015062016-01-15 11:15:24 -0700611 if implicit:
Stephen Warrena2ec5602016-01-26 13:41:31 -0700612 self.f.write('<span class="implicit">')
Stephen Warrend2015062016-01-15 11:15:24 -0700613 self.f.write(self._escape(data))
614 if implicit:
Stephen Warrena2ec5602016-01-26 13:41:31 -0700615 self.f.write('</span>')
Stephen Warrend2015062016-01-15 11:15:24 -0700616 self.last_stream = stream
617
618 def flush(self):
Stephen Warrene8debf32016-01-26 13:41:30 -0700619 """Flush the log stream, to ensure correct log interleaving.
Stephen Warrend2015062016-01-15 11:15:24 -0700620
621 Args:
622 None.
623
624 Returns:
625 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700626 """
Stephen Warrend2015062016-01-15 11:15:24 -0700627
628 self.f.flush()