blob: 442edada1257acbb810f8023a539abd1f324f960 [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0
Stephen Warren10e50632016-01-15 11:15:24 -07002# Copyright (c) 2015 Stephen Warren
3# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
Stephen Warren10e50632016-01-15 11:15:24 -07004
Heinrich Schuchardtffa0a232021-11-23 00:01:45 +01005"""
6Generate an HTML-formatted log file containing multiple streams of data,
7each represented in a well-delineated/-structured fashion.
8"""
Stephen Warren10e50632016-01-15 11:15:24 -07009
Stephen Warrenb1c556a2017-10-27 11:04:08 -060010import datetime
Tom Rini7f24c192019-10-24 11:59:20 -040011import html
Stephen Warren10e50632016-01-15 11:15:24 -070012import os.path
13import shutil
14import subprocess
15
16mod_dir = os.path.dirname(os.path.abspath(__file__))
17
18class LogfileStream(object):
Stephen Warren75e731e2016-01-26 13:41:30 -070019 """A file-like object used to write a single logical stream of data into
Stephen Warren10e50632016-01-15 11:15:24 -070020 a multiplexed log file. Objects of this type should be created by factory
Stephen Warren75e731e2016-01-26 13:41:30 -070021 functions in the Logfile class rather than directly."""
Stephen Warren10e50632016-01-15 11:15:24 -070022
23 def __init__(self, logfile, name, chained_file):
Stephen Warren75e731e2016-01-26 13:41:30 -070024 """Initialize a new object.
Stephen Warren10e50632016-01-15 11:15:24 -070025
26 Args:
27 logfile: The Logfile object to log to.
28 name: The name of this log stream.
29 chained_file: The file-like object to which all stream data should be
30 logged to in addition to logfile. Can be None.
31
32 Returns:
33 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -070034 """
Stephen Warren10e50632016-01-15 11:15:24 -070035
36 self.logfile = logfile
37 self.name = name
38 self.chained_file = chained_file
39
40 def close(self):
Stephen Warren75e731e2016-01-26 13:41:30 -070041 """Dummy function so that this class is "file-like".
Stephen Warren10e50632016-01-15 11:15:24 -070042
43 Args:
44 None.
45
46 Returns:
47 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -070048 """
Stephen Warren10e50632016-01-15 11:15:24 -070049
50 pass
51
52 def write(self, data, implicit=False):
Stephen Warren75e731e2016-01-26 13:41:30 -070053 """Write data to the log stream.
Stephen Warren10e50632016-01-15 11:15:24 -070054
55 Args:
Tom Rini6a990412019-10-24 11:59:21 -040056 data: The data to write to the file.
Stephen Warren10e50632016-01-15 11:15:24 -070057 implicit: Boolean indicating whether data actually appeared in the
58 stream, or was implicitly generated. A valid use-case is to
59 repeat a shell prompt at the start of each separate log
60 section, which makes the log sections more readable in
61 isolation.
62
63 Returns:
64 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -070065 """
Stephen Warren10e50632016-01-15 11:15:24 -070066
67 self.logfile.write(self, data, implicit)
68 if self.chained_file:
Tom Rini6a990412019-10-24 11:59:21 -040069 # Chained file is console, convert things a little
70 self.chained_file.write((data.encode('ascii', 'replace')).decode())
Stephen Warren10e50632016-01-15 11:15:24 -070071
72 def flush(self):
Stephen Warren75e731e2016-01-26 13:41:30 -070073 """Flush the log stream, to ensure correct log interleaving.
Stephen Warren10e50632016-01-15 11:15:24 -070074
75 Args:
76 None.
77
78 Returns:
79 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -070080 """
Stephen Warren10e50632016-01-15 11:15:24 -070081
82 self.logfile.flush()
83 if self.chained_file:
84 self.chained_file.flush()
85
86class RunAndLog(object):
Stephen Warren75e731e2016-01-26 13:41:30 -070087 """A utility object used to execute sub-processes and log their output to
Stephen Warren10e50632016-01-15 11:15:24 -070088 a multiplexed log file. Objects of this type should be created by factory
Stephen Warren75e731e2016-01-26 13:41:30 -070089 functions in the Logfile class rather than directly."""
Stephen Warren10e50632016-01-15 11:15:24 -070090
91 def __init__(self, logfile, name, chained_file):
Stephen Warren75e731e2016-01-26 13:41:30 -070092 """Initialize a new object.
Stephen Warren10e50632016-01-15 11:15:24 -070093
94 Args:
95 logfile: The Logfile object to log to.
96 name: The name of this log stream or sub-process.
97 chained_file: The file-like object to which all stream data should
98 be logged to in addition to logfile. Can be None.
99
100 Returns:
101 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700102 """
Stephen Warren10e50632016-01-15 11:15:24 -0700103
104 self.logfile = logfile
105 self.name = name
106 self.chained_file = chained_file
Simon Glass4134caf2016-07-03 09:40:38 -0600107 self.output = None
Simon Glass95adb802016-07-31 17:35:03 -0600108 self.exit_status = None
Stephen Warren10e50632016-01-15 11:15:24 -0700109
110 def close(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700111 """Clean up any resources managed by this object."""
Stephen Warren10e50632016-01-15 11:15:24 -0700112 pass
113
Stephen Warren118e37e2016-01-22 12:30:11 -0700114 def run(self, cmd, cwd=None, ignore_errors=False):
Stephen Warren75e731e2016-01-26 13:41:30 -0700115 """Run a command as a sub-process, and log the results.
Stephen Warren10e50632016-01-15 11:15:24 -0700116
Simon Glass4134caf2016-07-03 09:40:38 -0600117 The output is available at self.output which can be useful if there is
118 an exception.
119
Stephen Warren10e50632016-01-15 11:15:24 -0700120 Args:
121 cmd: The command to execute.
122 cwd: The directory to run the command in. Can be None to use the
123 current directory.
Stephen Warren118e37e2016-01-22 12:30:11 -0700124 ignore_errors: Indicate whether to ignore errors. If True, the
125 function will simply return if the command cannot be executed
126 or exits with an error code, otherwise an exception will be
127 raised if such problems occur.
Stephen Warren10e50632016-01-15 11:15:24 -0700128
129 Returns:
Simon Glass9f3c9e92016-07-03 09:40:37 -0600130 The output as a string.
Stephen Warren75e731e2016-01-26 13:41:30 -0700131 """
Stephen Warren10e50632016-01-15 11:15:24 -0700132
Stephen Warren3deb8962016-01-26 13:41:31 -0700133 msg = '+' + ' '.join(cmd) + '\n'
Stephen Warren10e50632016-01-15 11:15:24 -0700134 if self.chained_file:
135 self.chained_file.write(msg)
136 self.logfile.write(self, msg)
137
138 try:
139 p = subprocess.Popen(cmd, cwd=cwd,
140 stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
141 (stdout, stderr) = p.communicate()
Tom Rini6a990412019-10-24 11:59:21 -0400142 if stdout is not None:
143 stdout = stdout.decode('utf-8')
144 if stderr is not None:
145 stderr = stderr.decode('utf-8')
Stephen Warren10e50632016-01-15 11:15:24 -0700146 output = ''
147 if stdout:
148 if stderr:
149 output += 'stdout:\n'
150 output += stdout
151 if stderr:
152 if stdout:
153 output += 'stderr:\n'
154 output += stderr
155 exit_status = p.returncode
156 exception = None
157 except subprocess.CalledProcessError as cpe:
158 output = cpe.output
159 exit_status = cpe.returncode
160 exception = cpe
161 except Exception as e:
162 output = ''
163 exit_status = 0
164 exception = e
165 if output and not output.endswith('\n'):
166 output += '\n'
Stephen Warren118e37e2016-01-22 12:30:11 -0700167 if exit_status and not exception and not ignore_errors:
Stephen Warren10e50632016-01-15 11:15:24 -0700168 exception = Exception('Exit code: ' + str(exit_status))
169 if exception:
170 output += str(exception) + '\n'
171 self.logfile.write(self, output)
172 if self.chained_file:
173 self.chained_file.write(output)
Stephen Warrenb1c556a2017-10-27 11:04:08 -0600174 self.logfile.timestamp()
Simon Glass4134caf2016-07-03 09:40:38 -0600175
176 # Store the output so it can be accessed if we raise an exception.
177 self.output = output
Simon Glass95adb802016-07-31 17:35:03 -0600178 self.exit_status = exit_status
Stephen Warren10e50632016-01-15 11:15:24 -0700179 if exception:
180 raise exception
Simon Glass9f3c9e92016-07-03 09:40:37 -0600181 return output
Stephen Warren10e50632016-01-15 11:15:24 -0700182
Heinrich Schuchardtffa0a232021-11-23 00:01:45 +0100183class SectionCtxMgr:
Stephen Warren75e731e2016-01-26 13:41:30 -0700184 """A context manager for Python's "with" statement, which allows a certain
Stephen Warren10e50632016-01-15 11:15:24 -0700185 portion of test code to be logged to a separate section of the log file.
186 Objects of this type should be created by factory functions in the Logfile
Stephen Warren75e731e2016-01-26 13:41:30 -0700187 class rather than directly."""
Stephen Warren10e50632016-01-15 11:15:24 -0700188
Stephen Warrene3f2a502016-02-03 16:46:34 -0700189 def __init__(self, log, marker, anchor):
Stephen Warren75e731e2016-01-26 13:41:30 -0700190 """Initialize a new object.
Stephen Warren10e50632016-01-15 11:15:24 -0700191
192 Args:
193 log: The Logfile object to log to.
194 marker: The name of the nested log section.
Stephen Warrene3f2a502016-02-03 16:46:34 -0700195 anchor: The anchor value to pass to start_section().
Stephen Warren10e50632016-01-15 11:15:24 -0700196
197 Returns:
198 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700199 """
Stephen Warren10e50632016-01-15 11:15:24 -0700200
201 self.log = log
202 self.marker = marker
Stephen Warrene3f2a502016-02-03 16:46:34 -0700203 self.anchor = anchor
Stephen Warren10e50632016-01-15 11:15:24 -0700204
205 def __enter__(self):
Stephen Warrene3f2a502016-02-03 16:46:34 -0700206 self.anchor = self.log.start_section(self.marker, self.anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700207
208 def __exit__(self, extype, value, traceback):
209 self.log.end_section(self.marker)
210
Heinrich Schuchardtffa0a232021-11-23 00:01:45 +0100211class Logfile:
Stephen Warren75e731e2016-01-26 13:41:30 -0700212 """Generates an HTML-formatted log file containing multiple streams of
213 data, each represented in a well-delineated/-structured fashion."""
Stephen Warren10e50632016-01-15 11:15:24 -0700214
215 def __init__(self, fn):
Stephen Warren75e731e2016-01-26 13:41:30 -0700216 """Initialize a new object.
Stephen Warren10e50632016-01-15 11:15:24 -0700217
218 Args:
219 fn: The filename to write to.
220
221 Returns:
222 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700223 """
Stephen Warren10e50632016-01-15 11:15:24 -0700224
Tom Rini6a990412019-10-24 11:59:21 -0400225 self.f = open(fn, 'wt', encoding='utf-8')
Stephen Warren10e50632016-01-15 11:15:24 -0700226 self.last_stream = None
227 self.blocks = []
228 self.cur_evt = 1
Stephen Warrene3f2a502016-02-03 16:46:34 -0700229 self.anchor = 0
Stephen Warrenb1c556a2017-10-27 11:04:08 -0600230 self.timestamp_start = self._get_time()
231 self.timestamp_prev = self.timestamp_start
232 self.timestamp_blocks = []
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700233 self.seen_warning = False
Stephen Warrene3f2a502016-02-03 16:46:34 -0700234
Stephen Warren3deb8962016-01-26 13:41:31 -0700235 shutil.copy(mod_dir + '/multiplexed_log.css', os.path.dirname(fn))
236 self.f.write('''\
Stephen Warren10e50632016-01-15 11:15:24 -0700237<html>
238<head>
239<link rel="stylesheet" type="text/css" href="multiplexed_log.css">
Stephen Warrene3f2a502016-02-03 16:46:34 -0700240<script src="http://code.jquery.com/jquery.min.js"></script>
241<script>
242$(document).ready(function () {
243 // Copy status report HTML to start of log for easy access
244 sts = $(".block#status_report")[0].outerHTML;
245 $("tt").prepend(sts);
246
247 // Add expand/contract buttons to all block headers
248 btns = "<span class=\\\"block-expand hidden\\\">[+] </span>" +
249 "<span class=\\\"block-contract\\\">[-] </span>";
250 $(".block-header").prepend(btns);
251
252 // Pre-contract all blocks which passed, leaving only problem cases
253 // expanded, to highlight issues the user should look at.
254 // Only top-level blocks (sections) should have any status
255 passed_bcs = $(".block-content:has(.status-pass)");
256 // Some blocks might have multiple status entries (e.g. the status
257 // report), so take care not to hide blocks with partial success.
258 passed_bcs = passed_bcs.not(":has(.status-fail)");
259 passed_bcs = passed_bcs.not(":has(.status-xfail)");
260 passed_bcs = passed_bcs.not(":has(.status-xpass)");
261 passed_bcs = passed_bcs.not(":has(.status-skipped)");
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700262 passed_bcs = passed_bcs.not(":has(.status-warning)");
Stephen Warrene3f2a502016-02-03 16:46:34 -0700263 // Hide the passed blocks
264 passed_bcs.addClass("hidden");
265 // Flip the expand/contract button hiding for those blocks.
266 bhs = passed_bcs.parent().children(".block-header")
267 bhs.children(".block-expand").removeClass("hidden");
268 bhs.children(".block-contract").addClass("hidden");
269
270 // Add click handler to block headers.
271 // The handler expands/contracts the block.
272 $(".block-header").on("click", function (e) {
273 var header = $(this);
274 var content = header.next(".block-content");
275 var expanded = !content.hasClass("hidden");
276 if (expanded) {
277 content.addClass("hidden");
278 header.children(".block-expand").first().removeClass("hidden");
279 header.children(".block-contract").first().addClass("hidden");
280 } else {
281 header.children(".block-contract").first().removeClass("hidden");
282 header.children(".block-expand").first().addClass("hidden");
283 content.removeClass("hidden");
284 }
285 });
286
287 // When clicking on a link, expand the target block
288 $("a").on("click", function (e) {
289 var block = $($(this).attr("href"));
290 var header = block.children(".block-header");
291 var content = block.children(".block-content").first();
292 header.children(".block-contract").first().removeClass("hidden");
293 header.children(".block-expand").first().addClass("hidden");
294 content.removeClass("hidden");
295 });
296});
297</script>
Stephen Warren10e50632016-01-15 11:15:24 -0700298</head>
299<body>
300<tt>
Stephen Warren3deb8962016-01-26 13:41:31 -0700301''')
Stephen Warren10e50632016-01-15 11:15:24 -0700302
303 def close(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700304 """Close the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700305
306 After calling this function, no more data may be written to the log.
307
308 Args:
309 None.
310
311 Returns:
312 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700313 """
Stephen Warren10e50632016-01-15 11:15:24 -0700314
Stephen Warren3deb8962016-01-26 13:41:31 -0700315 self.f.write('''\
Stephen Warren10e50632016-01-15 11:15:24 -0700316</tt>
317</body>
318</html>
Stephen Warren3deb8962016-01-26 13:41:31 -0700319''')
Stephen Warren10e50632016-01-15 11:15:24 -0700320 self.f.close()
321
322 # The set of characters that should be represented as hexadecimal codes in
323 # the log file.
Simon Glassb6b30652018-10-01 21:12:34 -0600324 _nonprint = {ord('%')}
Heinrich Schuchardtffa0a232021-11-23 00:01:45 +0100325 _nonprint.update(c for c in range(0, 32) if c not in (9, 10))
326 _nonprint.update(range(127, 256))
Stephen Warren10e50632016-01-15 11:15:24 -0700327
328 def _escape(self, data):
Stephen Warren75e731e2016-01-26 13:41:30 -0700329 """Render data format suitable for inclusion in an HTML document.
Stephen Warren10e50632016-01-15 11:15:24 -0700330
331 This includes HTML-escaping certain characters, and translating
332 control characters to a hexadecimal representation.
333
334 Args:
335 data: The raw string data to be escaped.
336
337 Returns:
338 An escaped version of the data.
Stephen Warren75e731e2016-01-26 13:41:30 -0700339 """
Stephen Warren10e50632016-01-15 11:15:24 -0700340
Stephen Warren3deb8962016-01-26 13:41:31 -0700341 data = data.replace(chr(13), '')
Simon Glassb6b30652018-10-01 21:12:34 -0600342 data = ''.join((ord(c) in self._nonprint) and ('%%%02x' % ord(c)) or
Stephen Warren10e50632016-01-15 11:15:24 -0700343 c for c in data)
Tom Rini7f24c192019-10-24 11:59:20 -0400344 data = html.escape(data)
Stephen Warren10e50632016-01-15 11:15:24 -0700345 return data
346
347 def _terminate_stream(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700348 """Write HTML to the log file to terminate the current stream's data.
Stephen Warren10e50632016-01-15 11:15:24 -0700349
350 Args:
351 None.
352
353 Returns:
354 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700355 """
Stephen Warren10e50632016-01-15 11:15:24 -0700356
357 self.cur_evt += 1
358 if not self.last_stream:
359 return
Stephen Warren3deb8962016-01-26 13:41:31 -0700360 self.f.write('</pre>\n')
Stephen Warrene3f2a502016-02-03 16:46:34 -0700361 self.f.write('<div class="stream-trailer block-trailer">End stream: ' +
Stephen Warren3deb8962016-01-26 13:41:31 -0700362 self.last_stream.name + '</div>\n')
363 self.f.write('</div>\n')
Stephen Warrene3f2a502016-02-03 16:46:34 -0700364 self.f.write('</div>\n')
Stephen Warren10e50632016-01-15 11:15:24 -0700365 self.last_stream = None
366
Stephen Warrene3f2a502016-02-03 16:46:34 -0700367 def _note(self, note_type, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700368 """Write a note or one-off message to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700369
370 Args:
371 note_type: The type of note. This must be a value supported by the
372 accompanying multiplexed_log.css.
373 msg: The note/message to log.
Stephen Warrene3f2a502016-02-03 16:46:34 -0700374 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700375
376 Returns:
377 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700378 """
Stephen Warren10e50632016-01-15 11:15:24 -0700379
380 self._terminate_stream()
Stephen Warrene3f2a502016-02-03 16:46:34 -0700381 self.f.write('<div class="' + note_type + '">\n')
Stephen Warrene3f2a502016-02-03 16:46:34 -0700382 self.f.write('<pre>')
Stephen Warrene0d2aee2017-09-18 11:50:47 -0600383 if anchor:
384 self.f.write('<a href="#%s">' % anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700385 self.f.write(self._escape(msg))
Stephen Warrene3f2a502016-02-03 16:46:34 -0700386 if anchor:
Stephen Warrene0d2aee2017-09-18 11:50:47 -0600387 self.f.write('</a>')
388 self.f.write('\n</pre>\n')
Stephen Warrene3f2a502016-02-03 16:46:34 -0700389 self.f.write('</div>\n')
Stephen Warren10e50632016-01-15 11:15:24 -0700390
Stephen Warrene3f2a502016-02-03 16:46:34 -0700391 def start_section(self, marker, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700392 """Begin a new nested section in the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700393
394 Args:
395 marker: The name of the section that is starting.
Stephen Warrene3f2a502016-02-03 16:46:34 -0700396 anchor: The value to use for the anchor. If None, a unique value
397 will be calculated and used
Stephen Warren10e50632016-01-15 11:15:24 -0700398
399 Returns:
Stephen Warrene3f2a502016-02-03 16:46:34 -0700400 Name of the HTML anchor emitted before section.
Stephen Warren75e731e2016-01-26 13:41:30 -0700401 """
Stephen Warren10e50632016-01-15 11:15:24 -0700402
403 self._terminate_stream()
404 self.blocks.append(marker)
Stephen Warrenb1c556a2017-10-27 11:04:08 -0600405 self.timestamp_blocks.append(self._get_time())
Stephen Warrene3f2a502016-02-03 16:46:34 -0700406 if not anchor:
407 self.anchor += 1
408 anchor = str(self.anchor)
Stephen Warren3deb8962016-01-26 13:41:31 -0700409 blk_path = '/'.join(self.blocks)
Stephen Warrene3f2a502016-02-03 16:46:34 -0700410 self.f.write('<div class="section block" id="' + anchor + '">\n')
411 self.f.write('<div class="section-header block-header">Section: ' +
412 blk_path + '</div>\n')
413 self.f.write('<div class="section-content block-content">\n')
Stephen Warrenb1c556a2017-10-27 11:04:08 -0600414 self.timestamp()
Stephen Warrene3f2a502016-02-03 16:46:34 -0700415
416 return anchor
Stephen Warren10e50632016-01-15 11:15:24 -0700417
418 def end_section(self, marker):
Stephen Warren75e731e2016-01-26 13:41:30 -0700419 """Terminate the current nested section in the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700420
421 This function validates proper nesting of start_section() and
422 end_section() calls. If a mismatch is found, an exception is raised.
423
424 Args:
425 marker: The name of the section that is ending.
426
427 Returns:
428 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700429 """
Stephen Warren10e50632016-01-15 11:15:24 -0700430
431 if (not self.blocks) or (marker != self.blocks[-1]):
Stephen Warren3deb8962016-01-26 13:41:31 -0700432 raise Exception('Block nesting mismatch: "%s" "%s"' %
433 (marker, '/'.join(self.blocks)))
Stephen Warren10e50632016-01-15 11:15:24 -0700434 self._terminate_stream()
Stephen Warrenb1c556a2017-10-27 11:04:08 -0600435 timestamp_now = self._get_time()
436 timestamp_section_start = self.timestamp_blocks.pop()
437 delta_section = timestamp_now - timestamp_section_start
438 self._note("timestamp",
439 "TIME: SINCE-SECTION: " + str(delta_section))
Stephen Warren3deb8962016-01-26 13:41:31 -0700440 blk_path = '/'.join(self.blocks)
Stephen Warrene3f2a502016-02-03 16:46:34 -0700441 self.f.write('<div class="section-trailer block-trailer">' +
442 'End section: ' + blk_path + '</div>\n')
443 self.f.write('</div>\n')
Stephen Warren3deb8962016-01-26 13:41:31 -0700444 self.f.write('</div>\n')
Stephen Warren10e50632016-01-15 11:15:24 -0700445 self.blocks.pop()
446
Stephen Warrene3f2a502016-02-03 16:46:34 -0700447 def section(self, marker, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700448 """Create a temporary section in the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700449
450 This function creates a context manager for Python's "with" statement,
451 which allows a certain portion of test code to be logged to a separate
452 section of the log file.
453
454 Usage:
455 with log.section("somename"):
456 some test code
457
458 Args:
459 marker: The name of the nested section.
Stephen Warrene3f2a502016-02-03 16:46:34 -0700460 anchor: The anchor value to pass to start_section().
Stephen Warren10e50632016-01-15 11:15:24 -0700461
462 Returns:
463 A context manager object.
Stephen Warren75e731e2016-01-26 13:41:30 -0700464 """
Stephen Warren10e50632016-01-15 11:15:24 -0700465
Stephen Warrene3f2a502016-02-03 16:46:34 -0700466 return SectionCtxMgr(self, marker, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700467
468 def error(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700469 """Write an error note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700470
471 Args:
472 msg: A message describing the error.
473
474 Returns:
475 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700476 """
Stephen Warren10e50632016-01-15 11:15:24 -0700477
478 self._note("error", msg)
479
480 def warning(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700481 """Write an warning note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700482
483 Args:
484 msg: A message describing the warning.
485
486 Returns:
487 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700488 """
Stephen Warren10e50632016-01-15 11:15:24 -0700489
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700490 self.seen_warning = True
Stephen Warren10e50632016-01-15 11:15:24 -0700491 self._note("warning", msg)
492
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700493 def get_and_reset_warning(self):
494 """Get and reset the log warning flag.
495
496 Args:
497 None
498
499 Returns:
500 Whether a warning was seen since the last call.
501 """
502
503 ret = self.seen_warning
504 self.seen_warning = False
505 return ret
506
Stephen Warren10e50632016-01-15 11:15:24 -0700507 def info(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700508 """Write an informational note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700509
510 Args:
511 msg: An informational message.
512
513 Returns:
514 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700515 """
Stephen Warren10e50632016-01-15 11:15:24 -0700516
517 self._note("info", msg)
518
519 def action(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700520 """Write an action note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700521
522 Args:
523 msg: A message describing the action that is being logged.
524
525 Returns:
526 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700527 """
Stephen Warren10e50632016-01-15 11:15:24 -0700528
529 self._note("action", msg)
530
Stephen Warrenb1c556a2017-10-27 11:04:08 -0600531 def _get_time(self):
532 return datetime.datetime.now()
533
534 def timestamp(self):
535 """Write a timestamp to the log file.
536
537 Args:
538 None
539
540 Returns:
541 Nothing.
542 """
543
544 timestamp_now = self._get_time()
545 delta_prev = timestamp_now - self.timestamp_prev
546 delta_start = timestamp_now - self.timestamp_start
547 self.timestamp_prev = timestamp_now
548
549 self._note("timestamp",
550 "TIME: NOW: " + timestamp_now.strftime("%Y/%m/%d %H:%M:%S.%f"))
551 self._note("timestamp",
552 "TIME: SINCE-PREV: " + str(delta_prev))
553 self._note("timestamp",
554 "TIME: SINCE-START: " + str(delta_start))
555
Stephen Warrene3f2a502016-02-03 16:46:34 -0700556 def status_pass(self, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700557 """Write a note to the log file describing test(s) which passed.
Stephen Warren10e50632016-01-15 11:15:24 -0700558
559 Args:
Stephen Warren25b05242016-01-27 23:57:51 -0700560 msg: A message describing the passed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700561 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700562
563 Returns:
564 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700565 """
Stephen Warren10e50632016-01-15 11:15:24 -0700566
Stephen Warrene3f2a502016-02-03 16:46:34 -0700567 self._note("status-pass", msg, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700568
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700569 def status_warning(self, msg, anchor=None):
570 """Write a note to the log file describing test(s) which passed.
571
572 Args:
573 msg: A message describing the passed test(s).
574 anchor: Optional internal link target.
575
576 Returns:
577 Nothing.
578 """
579
580 self._note("status-warning", msg, anchor)
581
Stephen Warrene3f2a502016-02-03 16:46:34 -0700582 def status_skipped(self, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700583 """Write a note to the log file describing skipped test(s).
Stephen Warren10e50632016-01-15 11:15:24 -0700584
585 Args:
Stephen Warren25b05242016-01-27 23:57:51 -0700586 msg: A message describing the skipped test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700587 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700588
589 Returns:
590 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700591 """
Stephen Warren10e50632016-01-15 11:15:24 -0700592
Stephen Warrene3f2a502016-02-03 16:46:34 -0700593 self._note("status-skipped", msg, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700594
Stephen Warrene3f2a502016-02-03 16:46:34 -0700595 def status_xfail(self, msg, anchor=None):
Stephen Warren25b05242016-01-27 23:57:51 -0700596 """Write a note to the log file describing xfailed test(s).
597
598 Args:
599 msg: A message describing the xfailed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700600 anchor: Optional internal link target.
Stephen Warren25b05242016-01-27 23:57:51 -0700601
602 Returns:
603 Nothing.
604 """
605
Stephen Warrene3f2a502016-02-03 16:46:34 -0700606 self._note("status-xfail", msg, anchor)
Stephen Warren25b05242016-01-27 23:57:51 -0700607
Stephen Warrene3f2a502016-02-03 16:46:34 -0700608 def status_xpass(self, msg, anchor=None):
Stephen Warren25b05242016-01-27 23:57:51 -0700609 """Write a note to the log file describing xpassed test(s).
610
611 Args:
612 msg: A message describing the xpassed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700613 anchor: Optional internal link target.
Stephen Warren25b05242016-01-27 23:57:51 -0700614
615 Returns:
616 Nothing.
617 """
618
Stephen Warrene3f2a502016-02-03 16:46:34 -0700619 self._note("status-xpass", msg, anchor)
Stephen Warren25b05242016-01-27 23:57:51 -0700620
Stephen Warrene3f2a502016-02-03 16:46:34 -0700621 def status_fail(self, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700622 """Write a note to the log file describing failed test(s).
Stephen Warren10e50632016-01-15 11:15:24 -0700623
624 Args:
Stephen Warren25b05242016-01-27 23:57:51 -0700625 msg: A message describing the failed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700626 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700627
628 Returns:
629 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700630 """
Stephen Warren10e50632016-01-15 11:15:24 -0700631
Stephen Warrene3f2a502016-02-03 16:46:34 -0700632 self._note("status-fail", msg, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700633
634 def get_stream(self, name, chained_file=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700635 """Create an object to log a single stream's data into the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700636
637 This creates a "file-like" object that can be written to in order to
638 write a single stream's data to the log file. The implementation will
639 handle any required interleaving of data (from multiple streams) in
640 the log, in a way that makes it obvious which stream each bit of data
641 came from.
642
643 Args:
644 name: The name of the stream.
645 chained_file: The file-like object to which all stream data should
646 be logged to in addition to this log. Can be None.
647
648 Returns:
649 A file-like object.
Stephen Warren75e731e2016-01-26 13:41:30 -0700650 """
Stephen Warren10e50632016-01-15 11:15:24 -0700651
652 return LogfileStream(self, name, chained_file)
653
654 def get_runner(self, name, chained_file=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700655 """Create an object that executes processes and logs their output.
Stephen Warren10e50632016-01-15 11:15:24 -0700656
657 Args:
658 name: The name of this sub-process.
659 chained_file: The file-like object to which all stream data should
660 be logged to in addition to logfile. Can be None.
661
662 Returns:
663 A RunAndLog object.
Stephen Warren75e731e2016-01-26 13:41:30 -0700664 """
Stephen Warren10e50632016-01-15 11:15:24 -0700665
666 return RunAndLog(self, name, chained_file)
667
668 def write(self, stream, data, implicit=False):
Stephen Warren75e731e2016-01-26 13:41:30 -0700669 """Write stream data into the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700670
671 This function should only be used by instances of LogfileStream or
672 RunAndLog.
673
674 Args:
675 stream: The stream whose data is being logged.
676 data: The data to log.
677 implicit: Boolean indicating whether data actually appeared in the
678 stream, or was implicitly generated. A valid use-case is to
679 repeat a shell prompt at the start of each separate log
680 section, which makes the log sections more readable in
681 isolation.
682
683 Returns:
684 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700685 """
Stephen Warren10e50632016-01-15 11:15:24 -0700686
687 if stream != self.last_stream:
688 self._terminate_stream()
Stephen Warrene3f2a502016-02-03 16:46:34 -0700689 self.f.write('<div class="stream block">\n')
690 self.f.write('<div class="stream-header block-header">Stream: ' +
691 stream.name + '</div>\n')
692 self.f.write('<div class="stream-content block-content">\n')
Stephen Warren3deb8962016-01-26 13:41:31 -0700693 self.f.write('<pre>')
Stephen Warren10e50632016-01-15 11:15:24 -0700694 if implicit:
Stephen Warren3deb8962016-01-26 13:41:31 -0700695 self.f.write('<span class="implicit">')
Stephen Warren10e50632016-01-15 11:15:24 -0700696 self.f.write(self._escape(data))
697 if implicit:
Stephen Warren3deb8962016-01-26 13:41:31 -0700698 self.f.write('</span>')
Stephen Warren10e50632016-01-15 11:15:24 -0700699 self.last_stream = stream
700
701 def flush(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700702 """Flush the log stream, to ensure correct log interleaving.
Stephen Warren10e50632016-01-15 11:15:24 -0700703
704 Args:
705 None.
706
707 Returns:
708 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700709 """
Stephen Warren10e50632016-01-15 11:15:24 -0700710
711 self.f.flush()