blob: 5e79075f2e64a682d2a59c630ca1ae4b23a6bdc8 [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
Simon Glass30027012021-10-23 17:25:57 -0600114 def run(self, cmd, cwd=None, ignore_errors=False, stdin=None):
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.
Simon Glass30027012021-10-23 17:25:57 -0600128 stdin: Input string to pass to the command as stdin (or None)
Stephen Warren10e50632016-01-15 11:15:24 -0700129
130 Returns:
Simon Glass9f3c9e92016-07-03 09:40:37 -0600131 The output as a string.
Stephen Warren75e731e2016-01-26 13:41:30 -0700132 """
Stephen Warren10e50632016-01-15 11:15:24 -0700133
Stephen Warren3deb8962016-01-26 13:41:31 -0700134 msg = '+' + ' '.join(cmd) + '\n'
Stephen Warren10e50632016-01-15 11:15:24 -0700135 if self.chained_file:
136 self.chained_file.write(msg)
137 self.logfile.write(self, msg)
138
139 try:
140 p = subprocess.Popen(cmd, cwd=cwd,
Simon Glass30027012021-10-23 17:25:57 -0600141 stdin=subprocess.PIPE if stdin else None,
142 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
143 (stdout, stderr) = p.communicate(input=stdin)
Tom Rini6a990412019-10-24 11:59:21 -0400144 if stdout is not None:
145 stdout = stdout.decode('utf-8')
146 if stderr is not None:
147 stderr = stderr.decode('utf-8')
Stephen Warren10e50632016-01-15 11:15:24 -0700148 output = ''
149 if stdout:
150 if stderr:
151 output += 'stdout:\n'
152 output += stdout
153 if stderr:
154 if stdout:
155 output += 'stderr:\n'
156 output += stderr
157 exit_status = p.returncode
158 exception = None
159 except subprocess.CalledProcessError as cpe:
160 output = cpe.output
161 exit_status = cpe.returncode
162 exception = cpe
163 except Exception as e:
164 output = ''
165 exit_status = 0
166 exception = e
167 if output and not output.endswith('\n'):
168 output += '\n'
Stephen Warren118e37e2016-01-22 12:30:11 -0700169 if exit_status and not exception and not ignore_errors:
Simon Glass150dc1a2021-10-23 17:26:12 -0600170 exception = ValueError('Exit code: ' + str(exit_status))
Stephen Warren10e50632016-01-15 11:15:24 -0700171 if exception:
172 output += str(exception) + '\n'
173 self.logfile.write(self, output)
174 if self.chained_file:
175 self.chained_file.write(output)
Stephen Warrenb1c556a2017-10-27 11:04:08 -0600176 self.logfile.timestamp()
Simon Glass4134caf2016-07-03 09:40:38 -0600177
178 # Store the output so it can be accessed if we raise an exception.
179 self.output = output
Simon Glass95adb802016-07-31 17:35:03 -0600180 self.exit_status = exit_status
Stephen Warren10e50632016-01-15 11:15:24 -0700181 if exception:
182 raise exception
Simon Glass9f3c9e92016-07-03 09:40:37 -0600183 return output
Stephen Warren10e50632016-01-15 11:15:24 -0700184
Heinrich Schuchardtffa0a232021-11-23 00:01:45 +0100185class SectionCtxMgr:
Stephen Warren75e731e2016-01-26 13:41:30 -0700186 """A context manager for Python's "with" statement, which allows a certain
Stephen Warren10e50632016-01-15 11:15:24 -0700187 portion of test code to be logged to a separate section of the log file.
188 Objects of this type should be created by factory functions in the Logfile
Stephen Warren75e731e2016-01-26 13:41:30 -0700189 class rather than directly."""
Stephen Warren10e50632016-01-15 11:15:24 -0700190
Stephen Warrene3f2a502016-02-03 16:46:34 -0700191 def __init__(self, log, marker, anchor):
Stephen Warren75e731e2016-01-26 13:41:30 -0700192 """Initialize a new object.
Stephen Warren10e50632016-01-15 11:15:24 -0700193
194 Args:
195 log: The Logfile object to log to.
196 marker: The name of the nested log section.
Stephen Warrene3f2a502016-02-03 16:46:34 -0700197 anchor: The anchor value to pass to start_section().
Stephen Warren10e50632016-01-15 11:15:24 -0700198
199 Returns:
200 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700201 """
Stephen Warren10e50632016-01-15 11:15:24 -0700202
203 self.log = log
204 self.marker = marker
Stephen Warrene3f2a502016-02-03 16:46:34 -0700205 self.anchor = anchor
Stephen Warren10e50632016-01-15 11:15:24 -0700206
207 def __enter__(self):
Stephen Warrene3f2a502016-02-03 16:46:34 -0700208 self.anchor = self.log.start_section(self.marker, self.anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700209
210 def __exit__(self, extype, value, traceback):
211 self.log.end_section(self.marker)
212
Heinrich Schuchardtffa0a232021-11-23 00:01:45 +0100213class Logfile:
Stephen Warren75e731e2016-01-26 13:41:30 -0700214 """Generates an HTML-formatted log file containing multiple streams of
215 data, each represented in a well-delineated/-structured fashion."""
Stephen Warren10e50632016-01-15 11:15:24 -0700216
217 def __init__(self, fn):
Stephen Warren75e731e2016-01-26 13:41:30 -0700218 """Initialize a new object.
Stephen Warren10e50632016-01-15 11:15:24 -0700219
220 Args:
221 fn: The filename to write to.
222
223 Returns:
224 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700225 """
Stephen Warren10e50632016-01-15 11:15:24 -0700226
Tom Rini6a990412019-10-24 11:59:21 -0400227 self.f = open(fn, 'wt', encoding='utf-8')
Stephen Warren10e50632016-01-15 11:15:24 -0700228 self.last_stream = None
229 self.blocks = []
230 self.cur_evt = 1
Stephen Warrene3f2a502016-02-03 16:46:34 -0700231 self.anchor = 0
Stephen Warrenb1c556a2017-10-27 11:04:08 -0600232 self.timestamp_start = self._get_time()
233 self.timestamp_prev = self.timestamp_start
234 self.timestamp_blocks = []
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700235 self.seen_warning = False
Stephen Warrene3f2a502016-02-03 16:46:34 -0700236
Stephen Warren3deb8962016-01-26 13:41:31 -0700237 shutil.copy(mod_dir + '/multiplexed_log.css', os.path.dirname(fn))
238 self.f.write('''\
Stephen Warren10e50632016-01-15 11:15:24 -0700239<html>
240<head>
241<link rel="stylesheet" type="text/css" href="multiplexed_log.css">
Stephen Warrene3f2a502016-02-03 16:46:34 -0700242<script src="http://code.jquery.com/jquery.min.js"></script>
243<script>
244$(document).ready(function () {
245 // Copy status report HTML to start of log for easy access
246 sts = $(".block#status_report")[0].outerHTML;
247 $("tt").prepend(sts);
248
249 // Add expand/contract buttons to all block headers
250 btns = "<span class=\\\"block-expand hidden\\\">[+] </span>" +
251 "<span class=\\\"block-contract\\\">[-] </span>";
252 $(".block-header").prepend(btns);
253
254 // Pre-contract all blocks which passed, leaving only problem cases
255 // expanded, to highlight issues the user should look at.
256 // Only top-level blocks (sections) should have any status
257 passed_bcs = $(".block-content:has(.status-pass)");
258 // Some blocks might have multiple status entries (e.g. the status
259 // report), so take care not to hide blocks with partial success.
260 passed_bcs = passed_bcs.not(":has(.status-fail)");
261 passed_bcs = passed_bcs.not(":has(.status-xfail)");
262 passed_bcs = passed_bcs.not(":has(.status-xpass)");
263 passed_bcs = passed_bcs.not(":has(.status-skipped)");
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700264 passed_bcs = passed_bcs.not(":has(.status-warning)");
Stephen Warrene3f2a502016-02-03 16:46:34 -0700265 // Hide the passed blocks
266 passed_bcs.addClass("hidden");
267 // Flip the expand/contract button hiding for those blocks.
268 bhs = passed_bcs.parent().children(".block-header")
269 bhs.children(".block-expand").removeClass("hidden");
270 bhs.children(".block-contract").addClass("hidden");
271
272 // Add click handler to block headers.
273 // The handler expands/contracts the block.
274 $(".block-header").on("click", function (e) {
275 var header = $(this);
276 var content = header.next(".block-content");
277 var expanded = !content.hasClass("hidden");
278 if (expanded) {
279 content.addClass("hidden");
280 header.children(".block-expand").first().removeClass("hidden");
281 header.children(".block-contract").first().addClass("hidden");
282 } else {
283 header.children(".block-contract").first().removeClass("hidden");
284 header.children(".block-expand").first().addClass("hidden");
285 content.removeClass("hidden");
286 }
287 });
288
289 // When clicking on a link, expand the target block
290 $("a").on("click", function (e) {
291 var block = $($(this).attr("href"));
292 var header = block.children(".block-header");
293 var content = block.children(".block-content").first();
294 header.children(".block-contract").first().removeClass("hidden");
295 header.children(".block-expand").first().addClass("hidden");
296 content.removeClass("hidden");
297 });
298});
299</script>
Stephen Warren10e50632016-01-15 11:15:24 -0700300</head>
301<body>
302<tt>
Stephen Warren3deb8962016-01-26 13:41:31 -0700303''')
Stephen Warren10e50632016-01-15 11:15:24 -0700304
305 def close(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700306 """Close the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700307
308 After calling this function, no more data may be written to the log.
309
310 Args:
311 None.
312
313 Returns:
314 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700315 """
Stephen Warren10e50632016-01-15 11:15:24 -0700316
Stephen Warren3deb8962016-01-26 13:41:31 -0700317 self.f.write('''\
Stephen Warren10e50632016-01-15 11:15:24 -0700318</tt>
319</body>
320</html>
Stephen Warren3deb8962016-01-26 13:41:31 -0700321''')
Stephen Warren10e50632016-01-15 11:15:24 -0700322 self.f.close()
323
324 # The set of characters that should be represented as hexadecimal codes in
325 # the log file.
Simon Glassb6b30652018-10-01 21:12:34 -0600326 _nonprint = {ord('%')}
Heinrich Schuchardtffa0a232021-11-23 00:01:45 +0100327 _nonprint.update(c for c in range(0, 32) if c not in (9, 10))
328 _nonprint.update(range(127, 256))
Stephen Warren10e50632016-01-15 11:15:24 -0700329
330 def _escape(self, data):
Stephen Warren75e731e2016-01-26 13:41:30 -0700331 """Render data format suitable for inclusion in an HTML document.
Stephen Warren10e50632016-01-15 11:15:24 -0700332
333 This includes HTML-escaping certain characters, and translating
334 control characters to a hexadecimal representation.
335
336 Args:
337 data: The raw string data to be escaped.
338
339 Returns:
340 An escaped version of the data.
Stephen Warren75e731e2016-01-26 13:41:30 -0700341 """
Stephen Warren10e50632016-01-15 11:15:24 -0700342
Stephen Warren3deb8962016-01-26 13:41:31 -0700343 data = data.replace(chr(13), '')
Simon Glassb6b30652018-10-01 21:12:34 -0600344 data = ''.join((ord(c) in self._nonprint) and ('%%%02x' % ord(c)) or
Stephen Warren10e50632016-01-15 11:15:24 -0700345 c for c in data)
Tom Rini7f24c192019-10-24 11:59:20 -0400346 data = html.escape(data)
Stephen Warren10e50632016-01-15 11:15:24 -0700347 return data
348
349 def _terminate_stream(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700350 """Write HTML to the log file to terminate the current stream's data.
Stephen Warren10e50632016-01-15 11:15:24 -0700351
352 Args:
353 None.
354
355 Returns:
356 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700357 """
Stephen Warren10e50632016-01-15 11:15:24 -0700358
359 self.cur_evt += 1
360 if not self.last_stream:
361 return
Stephen Warren3deb8962016-01-26 13:41:31 -0700362 self.f.write('</pre>\n')
Stephen Warrene3f2a502016-02-03 16:46:34 -0700363 self.f.write('<div class="stream-trailer block-trailer">End stream: ' +
Stephen Warren3deb8962016-01-26 13:41:31 -0700364 self.last_stream.name + '</div>\n')
365 self.f.write('</div>\n')
Stephen Warrene3f2a502016-02-03 16:46:34 -0700366 self.f.write('</div>\n')
Stephen Warren10e50632016-01-15 11:15:24 -0700367 self.last_stream = None
368
Stephen Warrene3f2a502016-02-03 16:46:34 -0700369 def _note(self, note_type, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700370 """Write a note or one-off message to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700371
372 Args:
373 note_type: The type of note. This must be a value supported by the
374 accompanying multiplexed_log.css.
375 msg: The note/message to log.
Stephen Warrene3f2a502016-02-03 16:46:34 -0700376 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700377
378 Returns:
379 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700380 """
Stephen Warren10e50632016-01-15 11:15:24 -0700381
382 self._terminate_stream()
Stephen Warrene3f2a502016-02-03 16:46:34 -0700383 self.f.write('<div class="' + note_type + '">\n')
Stephen Warrene3f2a502016-02-03 16:46:34 -0700384 self.f.write('<pre>')
Stephen Warrene0d2aee2017-09-18 11:50:47 -0600385 if anchor:
386 self.f.write('<a href="#%s">' % anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700387 self.f.write(self._escape(msg))
Stephen Warrene3f2a502016-02-03 16:46:34 -0700388 if anchor:
Stephen Warrene0d2aee2017-09-18 11:50:47 -0600389 self.f.write('</a>')
390 self.f.write('\n</pre>\n')
Stephen Warrene3f2a502016-02-03 16:46:34 -0700391 self.f.write('</div>\n')
Stephen Warren10e50632016-01-15 11:15:24 -0700392
Stephen Warrene3f2a502016-02-03 16:46:34 -0700393 def start_section(self, marker, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700394 """Begin a new nested section in the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700395
396 Args:
397 marker: The name of the section that is starting.
Stephen Warrene3f2a502016-02-03 16:46:34 -0700398 anchor: The value to use for the anchor. If None, a unique value
399 will be calculated and used
Stephen Warren10e50632016-01-15 11:15:24 -0700400
401 Returns:
Stephen Warrene3f2a502016-02-03 16:46:34 -0700402 Name of the HTML anchor emitted before section.
Stephen Warren75e731e2016-01-26 13:41:30 -0700403 """
Stephen Warren10e50632016-01-15 11:15:24 -0700404
405 self._terminate_stream()
406 self.blocks.append(marker)
Stephen Warrenb1c556a2017-10-27 11:04:08 -0600407 self.timestamp_blocks.append(self._get_time())
Stephen Warrene3f2a502016-02-03 16:46:34 -0700408 if not anchor:
409 self.anchor += 1
410 anchor = str(self.anchor)
Stephen Warren3deb8962016-01-26 13:41:31 -0700411 blk_path = '/'.join(self.blocks)
Stephen Warrene3f2a502016-02-03 16:46:34 -0700412 self.f.write('<div class="section block" id="' + anchor + '">\n')
413 self.f.write('<div class="section-header block-header">Section: ' +
414 blk_path + '</div>\n')
415 self.f.write('<div class="section-content block-content">\n')
Stephen Warrenb1c556a2017-10-27 11:04:08 -0600416 self.timestamp()
Stephen Warrene3f2a502016-02-03 16:46:34 -0700417
418 return anchor
Stephen Warren10e50632016-01-15 11:15:24 -0700419
420 def end_section(self, marker):
Stephen Warren75e731e2016-01-26 13:41:30 -0700421 """Terminate the current nested section in the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700422
423 This function validates proper nesting of start_section() and
424 end_section() calls. If a mismatch is found, an exception is raised.
425
426 Args:
427 marker: The name of the section that is ending.
428
429 Returns:
430 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700431 """
Stephen Warren10e50632016-01-15 11:15:24 -0700432
433 if (not self.blocks) or (marker != self.blocks[-1]):
Stephen Warren3deb8962016-01-26 13:41:31 -0700434 raise Exception('Block nesting mismatch: "%s" "%s"' %
435 (marker, '/'.join(self.blocks)))
Stephen Warren10e50632016-01-15 11:15:24 -0700436 self._terminate_stream()
Stephen Warrenb1c556a2017-10-27 11:04:08 -0600437 timestamp_now = self._get_time()
438 timestamp_section_start = self.timestamp_blocks.pop()
439 delta_section = timestamp_now - timestamp_section_start
440 self._note("timestamp",
441 "TIME: SINCE-SECTION: " + str(delta_section))
Stephen Warren3deb8962016-01-26 13:41:31 -0700442 blk_path = '/'.join(self.blocks)
Stephen Warrene3f2a502016-02-03 16:46:34 -0700443 self.f.write('<div class="section-trailer block-trailer">' +
444 'End section: ' + blk_path + '</div>\n')
445 self.f.write('</div>\n')
Stephen Warren3deb8962016-01-26 13:41:31 -0700446 self.f.write('</div>\n')
Stephen Warren10e50632016-01-15 11:15:24 -0700447 self.blocks.pop()
448
Stephen Warrene3f2a502016-02-03 16:46:34 -0700449 def section(self, marker, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700450 """Create a temporary section in the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700451
452 This function creates a context manager for Python's "with" statement,
453 which allows a certain portion of test code to be logged to a separate
454 section of the log file.
455
456 Usage:
457 with log.section("somename"):
458 some test code
459
460 Args:
461 marker: The name of the nested section.
Stephen Warrene3f2a502016-02-03 16:46:34 -0700462 anchor: The anchor value to pass to start_section().
Stephen Warren10e50632016-01-15 11:15:24 -0700463
464 Returns:
465 A context manager object.
Stephen Warren75e731e2016-01-26 13:41:30 -0700466 """
Stephen Warren10e50632016-01-15 11:15:24 -0700467
Stephen Warrene3f2a502016-02-03 16:46:34 -0700468 return SectionCtxMgr(self, marker, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700469
470 def error(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700471 """Write an error note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700472
473 Args:
474 msg: A message describing the error.
475
476 Returns:
477 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700478 """
Stephen Warren10e50632016-01-15 11:15:24 -0700479
480 self._note("error", msg)
481
482 def warning(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700483 """Write an warning note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700484
485 Args:
486 msg: A message describing the warning.
487
488 Returns:
489 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700490 """
Stephen Warren10e50632016-01-15 11:15:24 -0700491
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700492 self.seen_warning = True
Stephen Warren10e50632016-01-15 11:15:24 -0700493 self._note("warning", msg)
494
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700495 def get_and_reset_warning(self):
496 """Get and reset the log warning flag.
497
498 Args:
499 None
500
501 Returns:
502 Whether a warning was seen since the last call.
503 """
504
505 ret = self.seen_warning
506 self.seen_warning = False
507 return ret
508
Stephen Warren10e50632016-01-15 11:15:24 -0700509 def info(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700510 """Write an informational note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700511
512 Args:
513 msg: An informational message.
514
515 Returns:
516 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700517 """
Stephen Warren10e50632016-01-15 11:15:24 -0700518
519 self._note("info", msg)
520
521 def action(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700522 """Write an action note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700523
524 Args:
525 msg: A message describing the action that is being logged.
526
527 Returns:
528 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700529 """
Stephen Warren10e50632016-01-15 11:15:24 -0700530
531 self._note("action", msg)
532
Stephen Warrenb1c556a2017-10-27 11:04:08 -0600533 def _get_time(self):
534 return datetime.datetime.now()
535
536 def timestamp(self):
537 """Write a timestamp to the log file.
538
539 Args:
540 None
541
542 Returns:
543 Nothing.
544 """
545
546 timestamp_now = self._get_time()
547 delta_prev = timestamp_now - self.timestamp_prev
548 delta_start = timestamp_now - self.timestamp_start
549 self.timestamp_prev = timestamp_now
550
551 self._note("timestamp",
552 "TIME: NOW: " + timestamp_now.strftime("%Y/%m/%d %H:%M:%S.%f"))
553 self._note("timestamp",
554 "TIME: SINCE-PREV: " + str(delta_prev))
555 self._note("timestamp",
556 "TIME: SINCE-START: " + str(delta_start))
557
Stephen Warrene3f2a502016-02-03 16:46:34 -0700558 def status_pass(self, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700559 """Write a note to the log file describing test(s) which passed.
Stephen Warren10e50632016-01-15 11:15:24 -0700560
561 Args:
Stephen Warren25b05242016-01-27 23:57:51 -0700562 msg: A message describing the passed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700563 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700564
565 Returns:
566 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700567 """
Stephen Warren10e50632016-01-15 11:15:24 -0700568
Stephen Warrene3f2a502016-02-03 16:46:34 -0700569 self._note("status-pass", msg, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700570
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700571 def status_warning(self, msg, anchor=None):
572 """Write a note to the log file describing test(s) which passed.
573
574 Args:
575 msg: A message describing the passed test(s).
576 anchor: Optional internal link target.
577
578 Returns:
579 Nothing.
580 """
581
582 self._note("status-warning", msg, anchor)
583
Stephen Warrene3f2a502016-02-03 16:46:34 -0700584 def status_skipped(self, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700585 """Write a note to the log file describing skipped test(s).
Stephen Warren10e50632016-01-15 11:15:24 -0700586
587 Args:
Stephen Warren25b05242016-01-27 23:57:51 -0700588 msg: A message describing the skipped test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700589 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700590
591 Returns:
592 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700593 """
Stephen Warren10e50632016-01-15 11:15:24 -0700594
Stephen Warrene3f2a502016-02-03 16:46:34 -0700595 self._note("status-skipped", msg, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700596
Stephen Warrene3f2a502016-02-03 16:46:34 -0700597 def status_xfail(self, msg, anchor=None):
Stephen Warren25b05242016-01-27 23:57:51 -0700598 """Write a note to the log file describing xfailed test(s).
599
600 Args:
601 msg: A message describing the xfailed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700602 anchor: Optional internal link target.
Stephen Warren25b05242016-01-27 23:57:51 -0700603
604 Returns:
605 Nothing.
606 """
607
Stephen Warrene3f2a502016-02-03 16:46:34 -0700608 self._note("status-xfail", msg, anchor)
Stephen Warren25b05242016-01-27 23:57:51 -0700609
Stephen Warrene3f2a502016-02-03 16:46:34 -0700610 def status_xpass(self, msg, anchor=None):
Stephen Warren25b05242016-01-27 23:57:51 -0700611 """Write a note to the log file describing xpassed test(s).
612
613 Args:
614 msg: A message describing the xpassed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700615 anchor: Optional internal link target.
Stephen Warren25b05242016-01-27 23:57:51 -0700616
617 Returns:
618 Nothing.
619 """
620
Stephen Warrene3f2a502016-02-03 16:46:34 -0700621 self._note("status-xpass", msg, anchor)
Stephen Warren25b05242016-01-27 23:57:51 -0700622
Stephen Warrene3f2a502016-02-03 16:46:34 -0700623 def status_fail(self, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700624 """Write a note to the log file describing failed test(s).
Stephen Warren10e50632016-01-15 11:15:24 -0700625
626 Args:
Stephen Warren25b05242016-01-27 23:57:51 -0700627 msg: A message describing the failed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700628 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700629
630 Returns:
631 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700632 """
Stephen Warren10e50632016-01-15 11:15:24 -0700633
Stephen Warrene3f2a502016-02-03 16:46:34 -0700634 self._note("status-fail", msg, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700635
636 def get_stream(self, name, chained_file=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700637 """Create an object to log a single stream's data into the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700638
639 This creates a "file-like" object that can be written to in order to
640 write a single stream's data to the log file. The implementation will
641 handle any required interleaving of data (from multiple streams) in
642 the log, in a way that makes it obvious which stream each bit of data
643 came from.
644
645 Args:
646 name: The name of the stream.
647 chained_file: The file-like object to which all stream data should
648 be logged to in addition to this log. Can be None.
649
650 Returns:
651 A file-like object.
Stephen Warren75e731e2016-01-26 13:41:30 -0700652 """
Stephen Warren10e50632016-01-15 11:15:24 -0700653
654 return LogfileStream(self, name, chained_file)
655
656 def get_runner(self, name, chained_file=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700657 """Create an object that executes processes and logs their output.
Stephen Warren10e50632016-01-15 11:15:24 -0700658
659 Args:
660 name: The name of this sub-process.
661 chained_file: The file-like object to which all stream data should
662 be logged to in addition to logfile. Can be None.
663
664 Returns:
665 A RunAndLog object.
Stephen Warren75e731e2016-01-26 13:41:30 -0700666 """
Stephen Warren10e50632016-01-15 11:15:24 -0700667
668 return RunAndLog(self, name, chained_file)
669
670 def write(self, stream, data, implicit=False):
Stephen Warren75e731e2016-01-26 13:41:30 -0700671 """Write stream data into the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700672
673 This function should only be used by instances of LogfileStream or
674 RunAndLog.
675
676 Args:
677 stream: The stream whose data is being logged.
678 data: The data to log.
679 implicit: Boolean indicating whether data actually appeared in the
680 stream, or was implicitly generated. A valid use-case is to
681 repeat a shell prompt at the start of each separate log
682 section, which makes the log sections more readable in
683 isolation.
684
685 Returns:
686 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700687 """
Stephen Warren10e50632016-01-15 11:15:24 -0700688
689 if stream != self.last_stream:
690 self._terminate_stream()
Stephen Warrene3f2a502016-02-03 16:46:34 -0700691 self.f.write('<div class="stream block">\n')
692 self.f.write('<div class="stream-header block-header">Stream: ' +
693 stream.name + '</div>\n')
694 self.f.write('<div class="stream-content block-content">\n')
Stephen Warren3deb8962016-01-26 13:41:31 -0700695 self.f.write('<pre>')
Stephen Warren10e50632016-01-15 11:15:24 -0700696 if implicit:
Stephen Warren3deb8962016-01-26 13:41:31 -0700697 self.f.write('<span class="implicit">')
Stephen Warren10e50632016-01-15 11:15:24 -0700698 self.f.write(self._escape(data))
699 if implicit:
Stephen Warren3deb8962016-01-26 13:41:31 -0700700 self.f.write('</span>')
Stephen Warren10e50632016-01-15 11:15:24 -0700701 self.last_stream = stream
702
703 def flush(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700704 """Flush the log stream, to ensure correct log interleaving.
Stephen Warren10e50632016-01-15 11:15:24 -0700705
706 Args:
707 None.
708
709 Returns:
710 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700711 """
Stephen Warren10e50632016-01-15 11:15:24 -0700712
713 self.f.flush()