blob: 5e79075f2e64a682d2a59c630ca1ae4b23a6bdc8 [file] [log] [blame]
Tom Rini83d290c2018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0
Stephen Warrend2015062016-01-15 11:15:24 -07002# Copyright (c) 2015 Stephen Warren
3# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
Stephen Warrend2015062016-01-15 11:15:24 -07004
Heinrich Schuchardt09e40982021-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 Warrend2015062016-01-15 11:15:24 -07009
Stephen Warren9679d332017-10-27 11:04:08 -060010import datetime
Tom Rinife1193e2019-10-24 11:59:20 -040011import html
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -070019 """A file-like object used to write a single logical stream of data into
Stephen Warrend2015062016-01-15 11:15:24 -070020 a multiplexed log file. Objects of this type should be created by factory
Stephen Warrene8debf32016-01-26 13:41:30 -070021 functions in the Logfile class rather than directly."""
Stephen Warrend2015062016-01-15 11:15:24 -070022
23 def __init__(self, logfile, name, chained_file):
Stephen Warrene8debf32016-01-26 13:41:30 -070024 """Initialize a new object.
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -070034 """
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -070041 """Dummy function so that this class is "file-like".
Stephen Warrend2015062016-01-15 11:15:24 -070042
43 Args:
44 None.
45
46 Returns:
47 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -070048 """
Stephen Warrend2015062016-01-15 11:15:24 -070049
50 pass
51
52 def write(self, data, implicit=False):
Stephen Warrene8debf32016-01-26 13:41:30 -070053 """Write data to the log stream.
Stephen Warrend2015062016-01-15 11:15:24 -070054
55 Args:
Tom Rinifd31fc12019-10-24 11:59:21 -040056 data: The data to write to the file.
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -070065 """
Stephen Warrend2015062016-01-15 11:15:24 -070066
67 self.logfile.write(self, data, implicit)
68 if self.chained_file:
Tom Rinifd31fc12019-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 Warrend2015062016-01-15 11:15:24 -070071
72 def flush(self):
Stephen Warrene8debf32016-01-26 13:41:30 -070073 """Flush the log stream, to ensure correct log interleaving.
Stephen Warrend2015062016-01-15 11:15:24 -070074
75 Args:
76 None.
77
78 Returns:
79 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -070080 """
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -070087 """A utility object used to execute sub-processes and log their output to
Stephen Warrend2015062016-01-15 11:15:24 -070088 a multiplexed log file. Objects of this type should be created by factory
Stephen Warrene8debf32016-01-26 13:41:30 -070089 functions in the Logfile class rather than directly."""
Stephen Warrend2015062016-01-15 11:15:24 -070090
91 def __init__(self, logfile, name, chained_file):
Stephen Warrene8debf32016-01-26 13:41:30 -070092 """Initialize a new object.
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -0700102 """
Stephen Warrend2015062016-01-15 11:15:24 -0700103
104 self.logfile = logfile
105 self.name = name
106 self.chained_file = chained_file
Simon Glass86845bf2016-07-03 09:40:38 -0600107 self.output = None
Simon Glass7f64b182016-07-31 17:35:03 -0600108 self.exit_status = None
Stephen Warrend2015062016-01-15 11:15:24 -0700109
110 def close(self):
Stephen Warrene8debf32016-01-26 13:41:30 -0700111 """Clean up any resources managed by this object."""
Stephen Warrend2015062016-01-15 11:15:24 -0700112 pass
113
Simon Glass15156c92021-10-23 17:25:57 -0600114 def run(self, cmd, cwd=None, ignore_errors=False, stdin=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700115 """Run a command as a sub-process, and log the results.
Stephen Warrend2015062016-01-15 11:15:24 -0700116
Simon Glass86845bf2016-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 Warrend2015062016-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 Warren3f2faf72016-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 Glass15156c92021-10-23 17:25:57 -0600128 stdin: Input string to pass to the command as stdin (or None)
Stephen Warrend2015062016-01-15 11:15:24 -0700129
130 Returns:
Simon Glass3b8d9d92016-07-03 09:40:37 -0600131 The output as a string.
Stephen Warrene8debf32016-01-26 13:41:30 -0700132 """
Stephen Warrend2015062016-01-15 11:15:24 -0700133
Stephen Warrena2ec5602016-01-26 13:41:31 -0700134 msg = '+' + ' '.join(cmd) + '\n'
Stephen Warrend2015062016-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 Glass15156c92021-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 Rinifd31fc12019-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 Warrend2015062016-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 Warren3f2faf72016-01-22 12:30:11 -0700169 if exit_status and not exception and not ignore_errors:
Simon Glass452e8c92021-10-23 17:26:12 -0600170 exception = ValueError('Exit code: ' + str(exit_status))
Stephen Warrend2015062016-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 Warren9679d332017-10-27 11:04:08 -0600176 self.logfile.timestamp()
Simon Glass86845bf2016-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 Glass7f64b182016-07-31 17:35:03 -0600180 self.exit_status = exit_status
Stephen Warrend2015062016-01-15 11:15:24 -0700181 if exception:
182 raise exception
Simon Glass3b8d9d92016-07-03 09:40:37 -0600183 return output
Stephen Warrend2015062016-01-15 11:15:24 -0700184
Heinrich Schuchardt09e40982021-11-23 00:01:45 +0100185class SectionCtxMgr:
Stephen Warrene8debf32016-01-26 13:41:30 -0700186 """A context manager for Python's "with" statement, which allows a certain
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -0700189 class rather than directly."""
Stephen Warrend2015062016-01-15 11:15:24 -0700190
Stephen Warren83357fd2016-02-03 16:46:34 -0700191 def __init__(self, log, marker, anchor):
Stephen Warrene8debf32016-01-26 13:41:30 -0700192 """Initialize a new object.
Stephen Warrend2015062016-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 Warren83357fd2016-02-03 16:46:34 -0700197 anchor: The anchor value to pass to start_section().
Stephen Warrend2015062016-01-15 11:15:24 -0700198
199 Returns:
200 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700201 """
Stephen Warrend2015062016-01-15 11:15:24 -0700202
203 self.log = log
204 self.marker = marker
Stephen Warren83357fd2016-02-03 16:46:34 -0700205 self.anchor = anchor
Stephen Warrend2015062016-01-15 11:15:24 -0700206
207 def __enter__(self):
Stephen Warren83357fd2016-02-03 16:46:34 -0700208 self.anchor = self.log.start_section(self.marker, self.anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700209
210 def __exit__(self, extype, value, traceback):
211 self.log.end_section(self.marker)
212
Heinrich Schuchardt09e40982021-11-23 00:01:45 +0100213class Logfile:
Stephen Warrene8debf32016-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 Warrend2015062016-01-15 11:15:24 -0700216
217 def __init__(self, fn):
Stephen Warrene8debf32016-01-26 13:41:30 -0700218 """Initialize a new object.
Stephen Warrend2015062016-01-15 11:15:24 -0700219
220 Args:
221 fn: The filename to write to.
222
223 Returns:
224 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700225 """
Stephen Warrend2015062016-01-15 11:15:24 -0700226
Tom Rinifd31fc12019-10-24 11:59:21 -0400227 self.f = open(fn, 'wt', encoding='utf-8')
Stephen Warrend2015062016-01-15 11:15:24 -0700228 self.last_stream = None
229 self.blocks = []
230 self.cur_evt = 1
Stephen Warren83357fd2016-02-03 16:46:34 -0700231 self.anchor = 0
Stephen Warren9679d332017-10-27 11:04:08 -0600232 self.timestamp_start = self._get_time()
233 self.timestamp_prev = self.timestamp_start
234 self.timestamp_blocks = []
Stephen Warren32090e52018-02-20 12:51:55 -0700235 self.seen_warning = False
Stephen Warren83357fd2016-02-03 16:46:34 -0700236
Stephen Warrena2ec5602016-01-26 13:41:31 -0700237 shutil.copy(mod_dir + '/multiplexed_log.css', os.path.dirname(fn))
238 self.f.write('''\
Stephen Warrend2015062016-01-15 11:15:24 -0700239<html>
240<head>
241<link rel="stylesheet" type="text/css" href="multiplexed_log.css">
Stephen Warren83357fd2016-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 Warren32090e52018-02-20 12:51:55 -0700264 passed_bcs = passed_bcs.not(":has(.status-warning)");
Stephen Warren83357fd2016-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 Warrend2015062016-01-15 11:15:24 -0700300</head>
301<body>
302<tt>
Stephen Warrena2ec5602016-01-26 13:41:31 -0700303''')
Stephen Warrend2015062016-01-15 11:15:24 -0700304
305 def close(self):
Stephen Warrene8debf32016-01-26 13:41:30 -0700306 """Close the log file.
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -0700315 """
Stephen Warrend2015062016-01-15 11:15:24 -0700316
Stephen Warrena2ec5602016-01-26 13:41:31 -0700317 self.f.write('''\
Stephen Warrend2015062016-01-15 11:15:24 -0700318</tt>
319</body>
320</html>
Stephen Warrena2ec5602016-01-26 13:41:31 -0700321''')
Stephen Warrend2015062016-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 Glass87b05ee2018-10-01 21:12:34 -0600326 _nonprint = {ord('%')}
Heinrich Schuchardt09e40982021-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 Warrend2015062016-01-15 11:15:24 -0700329
330 def _escape(self, data):
Stephen Warrene8debf32016-01-26 13:41:30 -0700331 """Render data format suitable for inclusion in an HTML document.
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -0700341 """
Stephen Warrend2015062016-01-15 11:15:24 -0700342
Stephen Warrena2ec5602016-01-26 13:41:31 -0700343 data = data.replace(chr(13), '')
Simon Glass87b05ee2018-10-01 21:12:34 -0600344 data = ''.join((ord(c) in self._nonprint) and ('%%%02x' % ord(c)) or
Stephen Warrend2015062016-01-15 11:15:24 -0700345 c for c in data)
Tom Rinife1193e2019-10-24 11:59:20 -0400346 data = html.escape(data)
Stephen Warrend2015062016-01-15 11:15:24 -0700347 return data
348
349 def _terminate_stream(self):
Stephen Warrene8debf32016-01-26 13:41:30 -0700350 """Write HTML to the log file to terminate the current stream's data.
Stephen Warrend2015062016-01-15 11:15:24 -0700351
352 Args:
353 None.
354
355 Returns:
356 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700357 """
Stephen Warrend2015062016-01-15 11:15:24 -0700358
359 self.cur_evt += 1
360 if not self.last_stream:
361 return
Stephen Warrena2ec5602016-01-26 13:41:31 -0700362 self.f.write('</pre>\n')
Stephen Warren83357fd2016-02-03 16:46:34 -0700363 self.f.write('<div class="stream-trailer block-trailer">End stream: ' +
Stephen Warrena2ec5602016-01-26 13:41:31 -0700364 self.last_stream.name + '</div>\n')
365 self.f.write('</div>\n')
Stephen Warren83357fd2016-02-03 16:46:34 -0700366 self.f.write('</div>\n')
Stephen Warrend2015062016-01-15 11:15:24 -0700367 self.last_stream = None
368
Stephen Warren83357fd2016-02-03 16:46:34 -0700369 def _note(self, note_type, msg, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700370 """Write a note or one-off message to the log file.
Stephen Warrend2015062016-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 Warren83357fd2016-02-03 16:46:34 -0700376 anchor: Optional internal link target.
Stephen Warrend2015062016-01-15 11:15:24 -0700377
378 Returns:
379 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700380 """
Stephen Warrend2015062016-01-15 11:15:24 -0700381
382 self._terminate_stream()
Stephen Warren83357fd2016-02-03 16:46:34 -0700383 self.f.write('<div class="' + note_type + '">\n')
Stephen Warren83357fd2016-02-03 16:46:34 -0700384 self.f.write('<pre>')
Stephen Warren83357fd2016-02-03 16:46:34 -0700385 if anchor:
Stephen Warren117eeb72017-09-18 11:50:47 -0600386 self.f.write('<a href="#%s">' % anchor)
387 self.f.write(self._escape(msg))
388 if anchor:
389 self.f.write('</a>')
390 self.f.write('\n</pre>\n')
Stephen Warren83357fd2016-02-03 16:46:34 -0700391 self.f.write('</div>\n')
Stephen Warrend2015062016-01-15 11:15:24 -0700392
Stephen Warren83357fd2016-02-03 16:46:34 -0700393 def start_section(self, marker, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700394 """Begin a new nested section in the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700395
396 Args:
397 marker: The name of the section that is starting.
Stephen Warren83357fd2016-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 Warrend2015062016-01-15 11:15:24 -0700400
401 Returns:
Stephen Warren83357fd2016-02-03 16:46:34 -0700402 Name of the HTML anchor emitted before section.
Stephen Warrene8debf32016-01-26 13:41:30 -0700403 """
Stephen Warrend2015062016-01-15 11:15:24 -0700404
405 self._terminate_stream()
406 self.blocks.append(marker)
Stephen Warren9679d332017-10-27 11:04:08 -0600407 self.timestamp_blocks.append(self._get_time())
Stephen Warren83357fd2016-02-03 16:46:34 -0700408 if not anchor:
409 self.anchor += 1
410 anchor = str(self.anchor)
Stephen Warrena2ec5602016-01-26 13:41:31 -0700411 blk_path = '/'.join(self.blocks)
Stephen Warren83357fd2016-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 Warren9679d332017-10-27 11:04:08 -0600416 self.timestamp()
Stephen Warren83357fd2016-02-03 16:46:34 -0700417
418 return anchor
Stephen Warrend2015062016-01-15 11:15:24 -0700419
420 def end_section(self, marker):
Stephen Warrene8debf32016-01-26 13:41:30 -0700421 """Terminate the current nested section in the log file.
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -0700431 """
Stephen Warrend2015062016-01-15 11:15:24 -0700432
433 if (not self.blocks) or (marker != self.blocks[-1]):
Stephen Warrena2ec5602016-01-26 13:41:31 -0700434 raise Exception('Block nesting mismatch: "%s" "%s"' %
435 (marker, '/'.join(self.blocks)))
Stephen Warrend2015062016-01-15 11:15:24 -0700436 self._terminate_stream()
Stephen Warren9679d332017-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 Warrena2ec5602016-01-26 13:41:31 -0700442 blk_path = '/'.join(self.blocks)
Stephen Warren83357fd2016-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 Warrena2ec5602016-01-26 13:41:31 -0700446 self.f.write('</div>\n')
Stephen Warrend2015062016-01-15 11:15:24 -0700447 self.blocks.pop()
448
Stephen Warren83357fd2016-02-03 16:46:34 -0700449 def section(self, marker, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700450 """Create a temporary section in the log file.
Stephen Warrend2015062016-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 Warren83357fd2016-02-03 16:46:34 -0700462 anchor: The anchor value to pass to start_section().
Stephen Warrend2015062016-01-15 11:15:24 -0700463
464 Returns:
465 A context manager object.
Stephen Warrene8debf32016-01-26 13:41:30 -0700466 """
Stephen Warrend2015062016-01-15 11:15:24 -0700467
Stephen Warren83357fd2016-02-03 16:46:34 -0700468 return SectionCtxMgr(self, marker, anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700469
470 def error(self, msg):
Stephen Warrene8debf32016-01-26 13:41:30 -0700471 """Write an error note to the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700472
473 Args:
474 msg: A message describing the error.
475
476 Returns:
477 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700478 """
Stephen Warrend2015062016-01-15 11:15:24 -0700479
480 self._note("error", msg)
481
482 def warning(self, msg):
Stephen Warrene8debf32016-01-26 13:41:30 -0700483 """Write an warning note to the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700484
485 Args:
486 msg: A message describing the warning.
487
488 Returns:
489 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700490 """
Stephen Warrend2015062016-01-15 11:15:24 -0700491
Stephen Warren32090e52018-02-20 12:51:55 -0700492 self.seen_warning = True
Stephen Warrend2015062016-01-15 11:15:24 -0700493 self._note("warning", msg)
494
Stephen Warren32090e52018-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 Warrend2015062016-01-15 11:15:24 -0700509 def info(self, msg):
Stephen Warrene8debf32016-01-26 13:41:30 -0700510 """Write an informational note to the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700511
512 Args:
513 msg: An informational message.
514
515 Returns:
516 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700517 """
Stephen Warrend2015062016-01-15 11:15:24 -0700518
519 self._note("info", msg)
520
521 def action(self, msg):
Stephen Warrene8debf32016-01-26 13:41:30 -0700522 """Write an action note to the log file.
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -0700529 """
Stephen Warrend2015062016-01-15 11:15:24 -0700530
531 self._note("action", msg)
532
Stephen Warren9679d332017-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 Warren83357fd2016-02-03 16:46:34 -0700558 def status_pass(self, msg, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700559 """Write a note to the log file describing test(s) which passed.
Stephen Warrend2015062016-01-15 11:15:24 -0700560
561 Args:
Stephen Warren78b39cc2016-01-27 23:57:51 -0700562 msg: A message describing the passed test(s).
Stephen Warren83357fd2016-02-03 16:46:34 -0700563 anchor: Optional internal link target.
Stephen Warrend2015062016-01-15 11:15:24 -0700564
565 Returns:
566 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700567 """
Stephen Warrend2015062016-01-15 11:15:24 -0700568
Stephen Warren83357fd2016-02-03 16:46:34 -0700569 self._note("status-pass", msg, anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700570
Stephen Warren32090e52018-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 Warren83357fd2016-02-03 16:46:34 -0700584 def status_skipped(self, msg, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700585 """Write a note to the log file describing skipped test(s).
Stephen Warrend2015062016-01-15 11:15:24 -0700586
587 Args:
Stephen Warren78b39cc2016-01-27 23:57:51 -0700588 msg: A message describing the skipped test(s).
Stephen Warren83357fd2016-02-03 16:46:34 -0700589 anchor: Optional internal link target.
Stephen Warrend2015062016-01-15 11:15:24 -0700590
591 Returns:
592 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700593 """
Stephen Warrend2015062016-01-15 11:15:24 -0700594
Stephen Warren83357fd2016-02-03 16:46:34 -0700595 self._note("status-skipped", msg, anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700596
Stephen Warren83357fd2016-02-03 16:46:34 -0700597 def status_xfail(self, msg, anchor=None):
Stephen Warren78b39cc2016-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 Warren83357fd2016-02-03 16:46:34 -0700602 anchor: Optional internal link target.
Stephen Warren78b39cc2016-01-27 23:57:51 -0700603
604 Returns:
605 Nothing.
606 """
607
Stephen Warren83357fd2016-02-03 16:46:34 -0700608 self._note("status-xfail", msg, anchor)
Stephen Warren78b39cc2016-01-27 23:57:51 -0700609
Stephen Warren83357fd2016-02-03 16:46:34 -0700610 def status_xpass(self, msg, anchor=None):
Stephen Warren78b39cc2016-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 Warren83357fd2016-02-03 16:46:34 -0700615 anchor: Optional internal link target.
Stephen Warren78b39cc2016-01-27 23:57:51 -0700616
617 Returns:
618 Nothing.
619 """
620
Stephen Warren83357fd2016-02-03 16:46:34 -0700621 self._note("status-xpass", msg, anchor)
Stephen Warren78b39cc2016-01-27 23:57:51 -0700622
Stephen Warren83357fd2016-02-03 16:46:34 -0700623 def status_fail(self, msg, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700624 """Write a note to the log file describing failed test(s).
Stephen Warrend2015062016-01-15 11:15:24 -0700625
626 Args:
Stephen Warren78b39cc2016-01-27 23:57:51 -0700627 msg: A message describing the failed test(s).
Stephen Warren83357fd2016-02-03 16:46:34 -0700628 anchor: Optional internal link target.
Stephen Warrend2015062016-01-15 11:15:24 -0700629
630 Returns:
631 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700632 """
Stephen Warrend2015062016-01-15 11:15:24 -0700633
Stephen Warren83357fd2016-02-03 16:46:34 -0700634 self._note("status-fail", msg, anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700635
636 def get_stream(self, name, chained_file=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700637 """Create an object to log a single stream's data into the log file.
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -0700652 """
Stephen Warrend2015062016-01-15 11:15:24 -0700653
654 return LogfileStream(self, name, chained_file)
655
656 def get_runner(self, name, chained_file=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700657 """Create an object that executes processes and logs their output.
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -0700666 """
Stephen Warrend2015062016-01-15 11:15:24 -0700667
668 return RunAndLog(self, name, chained_file)
669
670 def write(self, stream, data, implicit=False):
Stephen Warrene8debf32016-01-26 13:41:30 -0700671 """Write stream data into the log file.
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -0700687 """
Stephen Warrend2015062016-01-15 11:15:24 -0700688
689 if stream != self.last_stream:
690 self._terminate_stream()
Stephen Warren83357fd2016-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 Warrena2ec5602016-01-26 13:41:31 -0700695 self.f.write('<pre>')
Stephen Warrend2015062016-01-15 11:15:24 -0700696 if implicit:
Stephen Warrena2ec5602016-01-26 13:41:31 -0700697 self.f.write('<span class="implicit">')
Stephen Warrend2015062016-01-15 11:15:24 -0700698 self.f.write(self._escape(data))
699 if implicit:
Stephen Warrena2ec5602016-01-26 13:41:31 -0700700 self.f.write('</span>')
Stephen Warrend2015062016-01-15 11:15:24 -0700701 self.last_stream = stream
702
703 def flush(self):
Stephen Warrene8debf32016-01-26 13:41:30 -0700704 """Flush the log stream, to ensure correct log interleaving.
Stephen Warrend2015062016-01-15 11:15:24 -0700705
706 Args:
707 None.
708
709 Returns:
710 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700711 """
Stephen Warrend2015062016-01-15 11:15:24 -0700712
713 self.f.flush()