blob: 63237594bb405ba3e1a97d7932eb4fba8c608c60 [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 Glass7e91bf82023-02-13 08:56:40 -0700114 def run(self, cmd, cwd=None, ignore_errors=False, stdin=None, env=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)
Simon Glass7e91bf82023-02-13 08:56:40 -0700129 env: Environment to use, or None to use the current one
Stephen Warrend2015062016-01-15 11:15:24 -0700130
131 Returns:
Simon Glass3b8d9d92016-07-03 09:40:37 -0600132 The output as a string.
Stephen Warrene8debf32016-01-26 13:41:30 -0700133 """
Stephen Warrend2015062016-01-15 11:15:24 -0700134
Stephen Warrena2ec5602016-01-26 13:41:31 -0700135 msg = '+' + ' '.join(cmd) + '\n'
Stephen Warrend2015062016-01-15 11:15:24 -0700136 if self.chained_file:
137 self.chained_file.write(msg)
138 self.logfile.write(self, msg)
139
140 try:
141 p = subprocess.Popen(cmd, cwd=cwd,
Simon Glass15156c92021-10-23 17:25:57 -0600142 stdin=subprocess.PIPE if stdin else None,
Simon Glass7e91bf82023-02-13 08:56:40 -0700143 stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env)
Simon Glass15156c92021-10-23 17:25:57 -0600144 (stdout, stderr) = p.communicate(input=stdin)
Tom Rinifd31fc12019-10-24 11:59:21 -0400145 if stdout is not None:
146 stdout = stdout.decode('utf-8')
147 if stderr is not None:
148 stderr = stderr.decode('utf-8')
Stephen Warrend2015062016-01-15 11:15:24 -0700149 output = ''
150 if stdout:
151 if stderr:
152 output += 'stdout:\n'
153 output += stdout
154 if stderr:
155 if stdout:
156 output += 'stderr:\n'
157 output += stderr
158 exit_status = p.returncode
159 exception = None
160 except subprocess.CalledProcessError as cpe:
161 output = cpe.output
162 exit_status = cpe.returncode
163 exception = cpe
164 except Exception as e:
165 output = ''
166 exit_status = 0
167 exception = e
168 if output and not output.endswith('\n'):
169 output += '\n'
Stephen Warren3f2faf72016-01-22 12:30:11 -0700170 if exit_status and not exception and not ignore_errors:
Simon Glass452e8c92021-10-23 17:26:12 -0600171 exception = ValueError('Exit code: ' + str(exit_status))
Stephen Warrend2015062016-01-15 11:15:24 -0700172 if exception:
173 output += str(exception) + '\n'
174 self.logfile.write(self, output)
175 if self.chained_file:
176 self.chained_file.write(output)
Stephen Warren9679d332017-10-27 11:04:08 -0600177 self.logfile.timestamp()
Simon Glass86845bf2016-07-03 09:40:38 -0600178
179 # Store the output so it can be accessed if we raise an exception.
180 self.output = output
Simon Glass7f64b182016-07-31 17:35:03 -0600181 self.exit_status = exit_status
Stephen Warrend2015062016-01-15 11:15:24 -0700182 if exception:
183 raise exception
Simon Glass3b8d9d92016-07-03 09:40:37 -0600184 return output
Stephen Warrend2015062016-01-15 11:15:24 -0700185
Heinrich Schuchardt09e40982021-11-23 00:01:45 +0100186class SectionCtxMgr:
Stephen Warrene8debf32016-01-26 13:41:30 -0700187 """A context manager for Python's "with" statement, which allows a certain
Stephen Warrend2015062016-01-15 11:15:24 -0700188 portion of test code to be logged to a separate section of the log file.
189 Objects of this type should be created by factory functions in the Logfile
Stephen Warrene8debf32016-01-26 13:41:30 -0700190 class rather than directly."""
Stephen Warrend2015062016-01-15 11:15:24 -0700191
Stephen Warren83357fd2016-02-03 16:46:34 -0700192 def __init__(self, log, marker, anchor):
Stephen Warrene8debf32016-01-26 13:41:30 -0700193 """Initialize a new object.
Stephen Warrend2015062016-01-15 11:15:24 -0700194
195 Args:
196 log: The Logfile object to log to.
197 marker: The name of the nested log section.
Stephen Warren83357fd2016-02-03 16:46:34 -0700198 anchor: The anchor value to pass to start_section().
Stephen Warrend2015062016-01-15 11:15:24 -0700199
200 Returns:
201 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700202 """
Stephen Warrend2015062016-01-15 11:15:24 -0700203
204 self.log = log
205 self.marker = marker
Stephen Warren83357fd2016-02-03 16:46:34 -0700206 self.anchor = anchor
Stephen Warrend2015062016-01-15 11:15:24 -0700207
208 def __enter__(self):
Stephen Warren83357fd2016-02-03 16:46:34 -0700209 self.anchor = self.log.start_section(self.marker, self.anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700210
211 def __exit__(self, extype, value, traceback):
212 self.log.end_section(self.marker)
213
Heinrich Schuchardt09e40982021-11-23 00:01:45 +0100214class Logfile:
Stephen Warrene8debf32016-01-26 13:41:30 -0700215 """Generates an HTML-formatted log file containing multiple streams of
216 data, each represented in a well-delineated/-structured fashion."""
Stephen Warrend2015062016-01-15 11:15:24 -0700217
218 def __init__(self, fn):
Stephen Warrene8debf32016-01-26 13:41:30 -0700219 """Initialize a new object.
Stephen Warrend2015062016-01-15 11:15:24 -0700220
221 Args:
222 fn: The filename to write to.
223
224 Returns:
225 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700226 """
Stephen Warrend2015062016-01-15 11:15:24 -0700227
Tom Rinifd31fc12019-10-24 11:59:21 -0400228 self.f = open(fn, 'wt', encoding='utf-8')
Stephen Warrend2015062016-01-15 11:15:24 -0700229 self.last_stream = None
230 self.blocks = []
231 self.cur_evt = 1
Stephen Warren83357fd2016-02-03 16:46:34 -0700232 self.anchor = 0
Stephen Warren9679d332017-10-27 11:04:08 -0600233 self.timestamp_start = self._get_time()
234 self.timestamp_prev = self.timestamp_start
235 self.timestamp_blocks = []
Stephen Warren32090e52018-02-20 12:51:55 -0700236 self.seen_warning = False
Stephen Warren83357fd2016-02-03 16:46:34 -0700237
Stephen Warrena2ec5602016-01-26 13:41:31 -0700238 shutil.copy(mod_dir + '/multiplexed_log.css', os.path.dirname(fn))
239 self.f.write('''\
Stephen Warrend2015062016-01-15 11:15:24 -0700240<html>
241<head>
242<link rel="stylesheet" type="text/css" href="multiplexed_log.css">
Stephen Warren83357fd2016-02-03 16:46:34 -0700243<script src="http://code.jquery.com/jquery.min.js"></script>
244<script>
245$(document).ready(function () {
246 // Copy status report HTML to start of log for easy access
247 sts = $(".block#status_report")[0].outerHTML;
248 $("tt").prepend(sts);
249
250 // Add expand/contract buttons to all block headers
251 btns = "<span class=\\\"block-expand hidden\\\">[+] </span>" +
252 "<span class=\\\"block-contract\\\">[-] </span>";
253 $(".block-header").prepend(btns);
254
255 // Pre-contract all blocks which passed, leaving only problem cases
256 // expanded, to highlight issues the user should look at.
257 // Only top-level blocks (sections) should have any status
258 passed_bcs = $(".block-content:has(.status-pass)");
259 // Some blocks might have multiple status entries (e.g. the status
260 // report), so take care not to hide blocks with partial success.
261 passed_bcs = passed_bcs.not(":has(.status-fail)");
262 passed_bcs = passed_bcs.not(":has(.status-xfail)");
263 passed_bcs = passed_bcs.not(":has(.status-xpass)");
264 passed_bcs = passed_bcs.not(":has(.status-skipped)");
Stephen Warren32090e52018-02-20 12:51:55 -0700265 passed_bcs = passed_bcs.not(":has(.status-warning)");
Stephen Warren83357fd2016-02-03 16:46:34 -0700266 // Hide the passed blocks
267 passed_bcs.addClass("hidden");
268 // Flip the expand/contract button hiding for those blocks.
269 bhs = passed_bcs.parent().children(".block-header")
270 bhs.children(".block-expand").removeClass("hidden");
271 bhs.children(".block-contract").addClass("hidden");
272
273 // Add click handler to block headers.
274 // The handler expands/contracts the block.
275 $(".block-header").on("click", function (e) {
276 var header = $(this);
277 var content = header.next(".block-content");
278 var expanded = !content.hasClass("hidden");
279 if (expanded) {
280 content.addClass("hidden");
281 header.children(".block-expand").first().removeClass("hidden");
282 header.children(".block-contract").first().addClass("hidden");
283 } else {
284 header.children(".block-contract").first().removeClass("hidden");
285 header.children(".block-expand").first().addClass("hidden");
286 content.removeClass("hidden");
287 }
288 });
289
290 // When clicking on a link, expand the target block
291 $("a").on("click", function (e) {
292 var block = $($(this).attr("href"));
293 var header = block.children(".block-header");
294 var content = block.children(".block-content").first();
295 header.children(".block-contract").first().removeClass("hidden");
296 header.children(".block-expand").first().addClass("hidden");
297 content.removeClass("hidden");
298 });
299});
300</script>
Stephen Warrend2015062016-01-15 11:15:24 -0700301</head>
302<body>
303<tt>
Stephen Warrena2ec5602016-01-26 13:41:31 -0700304''')
Stephen Warrend2015062016-01-15 11:15:24 -0700305
306 def close(self):
Stephen Warrene8debf32016-01-26 13:41:30 -0700307 """Close the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700308
309 After calling this function, no more data may be written to the log.
310
311 Args:
312 None.
313
314 Returns:
315 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700316 """
Stephen Warrend2015062016-01-15 11:15:24 -0700317
Stephen Warrena2ec5602016-01-26 13:41:31 -0700318 self.f.write('''\
Stephen Warrend2015062016-01-15 11:15:24 -0700319</tt>
320</body>
321</html>
Stephen Warrena2ec5602016-01-26 13:41:31 -0700322''')
Stephen Warrend2015062016-01-15 11:15:24 -0700323 self.f.close()
324
325 # The set of characters that should be represented as hexadecimal codes in
326 # the log file.
Simon Glass87b05ee2018-10-01 21:12:34 -0600327 _nonprint = {ord('%')}
Heinrich Schuchardt09e40982021-11-23 00:01:45 +0100328 _nonprint.update(c for c in range(0, 32) if c not in (9, 10))
329 _nonprint.update(range(127, 256))
Stephen Warrend2015062016-01-15 11:15:24 -0700330
331 def _escape(self, data):
Stephen Warrene8debf32016-01-26 13:41:30 -0700332 """Render data format suitable for inclusion in an HTML document.
Stephen Warrend2015062016-01-15 11:15:24 -0700333
334 This includes HTML-escaping certain characters, and translating
335 control characters to a hexadecimal representation.
336
337 Args:
338 data: The raw string data to be escaped.
339
340 Returns:
341 An escaped version of the data.
Stephen Warrene8debf32016-01-26 13:41:30 -0700342 """
Stephen Warrend2015062016-01-15 11:15:24 -0700343
Stephen Warrena2ec5602016-01-26 13:41:31 -0700344 data = data.replace(chr(13), '')
Simon Glass87b05ee2018-10-01 21:12:34 -0600345 data = ''.join((ord(c) in self._nonprint) and ('%%%02x' % ord(c)) or
Stephen Warrend2015062016-01-15 11:15:24 -0700346 c for c in data)
Tom Rinife1193e2019-10-24 11:59:20 -0400347 data = html.escape(data)
Stephen Warrend2015062016-01-15 11:15:24 -0700348 return data
349
350 def _terminate_stream(self):
Stephen Warrene8debf32016-01-26 13:41:30 -0700351 """Write HTML to the log file to terminate the current stream's data.
Stephen Warrend2015062016-01-15 11:15:24 -0700352
353 Args:
354 None.
355
356 Returns:
357 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700358 """
Stephen Warrend2015062016-01-15 11:15:24 -0700359
360 self.cur_evt += 1
361 if not self.last_stream:
362 return
Stephen Warrena2ec5602016-01-26 13:41:31 -0700363 self.f.write('</pre>\n')
Stephen Warren83357fd2016-02-03 16:46:34 -0700364 self.f.write('<div class="stream-trailer block-trailer">End stream: ' +
Stephen Warrena2ec5602016-01-26 13:41:31 -0700365 self.last_stream.name + '</div>\n')
366 self.f.write('</div>\n')
Stephen Warren83357fd2016-02-03 16:46:34 -0700367 self.f.write('</div>\n')
Stephen Warrend2015062016-01-15 11:15:24 -0700368 self.last_stream = None
369
Stephen Warren83357fd2016-02-03 16:46:34 -0700370 def _note(self, note_type, msg, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700371 """Write a note or one-off message to the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700372
373 Args:
374 note_type: The type of note. This must be a value supported by the
375 accompanying multiplexed_log.css.
376 msg: The note/message to log.
Stephen Warren83357fd2016-02-03 16:46:34 -0700377 anchor: Optional internal link target.
Stephen Warrend2015062016-01-15 11:15:24 -0700378
379 Returns:
380 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700381 """
Stephen Warrend2015062016-01-15 11:15:24 -0700382
383 self._terminate_stream()
Stephen Warren83357fd2016-02-03 16:46:34 -0700384 self.f.write('<div class="' + note_type + '">\n')
Stephen Warren83357fd2016-02-03 16:46:34 -0700385 self.f.write('<pre>')
Stephen Warren83357fd2016-02-03 16:46:34 -0700386 if anchor:
Stephen Warren117eeb72017-09-18 11:50:47 -0600387 self.f.write('<a href="#%s">' % anchor)
388 self.f.write(self._escape(msg))
389 if anchor:
390 self.f.write('</a>')
391 self.f.write('\n</pre>\n')
Stephen Warren83357fd2016-02-03 16:46:34 -0700392 self.f.write('</div>\n')
Stephen Warrend2015062016-01-15 11:15:24 -0700393
Stephen Warren83357fd2016-02-03 16:46:34 -0700394 def start_section(self, marker, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700395 """Begin a new nested section in the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700396
397 Args:
398 marker: The name of the section that is starting.
Stephen Warren83357fd2016-02-03 16:46:34 -0700399 anchor: The value to use for the anchor. If None, a unique value
400 will be calculated and used
Stephen Warrend2015062016-01-15 11:15:24 -0700401
402 Returns:
Stephen Warren83357fd2016-02-03 16:46:34 -0700403 Name of the HTML anchor emitted before section.
Stephen Warrene8debf32016-01-26 13:41:30 -0700404 """
Stephen Warrend2015062016-01-15 11:15:24 -0700405
406 self._terminate_stream()
407 self.blocks.append(marker)
Stephen Warren9679d332017-10-27 11:04:08 -0600408 self.timestamp_blocks.append(self._get_time())
Stephen Warren83357fd2016-02-03 16:46:34 -0700409 if not anchor:
410 self.anchor += 1
411 anchor = str(self.anchor)
Stephen Warrena2ec5602016-01-26 13:41:31 -0700412 blk_path = '/'.join(self.blocks)
Stephen Warren83357fd2016-02-03 16:46:34 -0700413 self.f.write('<div class="section block" id="' + anchor + '">\n')
414 self.f.write('<div class="section-header block-header">Section: ' +
415 blk_path + '</div>\n')
416 self.f.write('<div class="section-content block-content">\n')
Stephen Warren9679d332017-10-27 11:04:08 -0600417 self.timestamp()
Stephen Warren83357fd2016-02-03 16:46:34 -0700418
419 return anchor
Stephen Warrend2015062016-01-15 11:15:24 -0700420
421 def end_section(self, marker):
Stephen Warrene8debf32016-01-26 13:41:30 -0700422 """Terminate the current nested section in the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700423
424 This function validates proper nesting of start_section() and
425 end_section() calls. If a mismatch is found, an exception is raised.
426
427 Args:
428 marker: The name of the section that is ending.
429
430 Returns:
431 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700432 """
Stephen Warrend2015062016-01-15 11:15:24 -0700433
434 if (not self.blocks) or (marker != self.blocks[-1]):
Stephen Warrena2ec5602016-01-26 13:41:31 -0700435 raise Exception('Block nesting mismatch: "%s" "%s"' %
436 (marker, '/'.join(self.blocks)))
Stephen Warrend2015062016-01-15 11:15:24 -0700437 self._terminate_stream()
Stephen Warren9679d332017-10-27 11:04:08 -0600438 timestamp_now = self._get_time()
439 timestamp_section_start = self.timestamp_blocks.pop()
440 delta_section = timestamp_now - timestamp_section_start
441 self._note("timestamp",
442 "TIME: SINCE-SECTION: " + str(delta_section))
Stephen Warrena2ec5602016-01-26 13:41:31 -0700443 blk_path = '/'.join(self.blocks)
Stephen Warren83357fd2016-02-03 16:46:34 -0700444 self.f.write('<div class="section-trailer block-trailer">' +
445 'End section: ' + blk_path + '</div>\n')
446 self.f.write('</div>\n')
Stephen Warrena2ec5602016-01-26 13:41:31 -0700447 self.f.write('</div>\n')
Stephen Warrend2015062016-01-15 11:15:24 -0700448 self.blocks.pop()
449
Stephen Warren83357fd2016-02-03 16:46:34 -0700450 def section(self, marker, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700451 """Create a temporary section in the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700452
453 This function creates a context manager for Python's "with" statement,
454 which allows a certain portion of test code to be logged to a separate
455 section of the log file.
456
457 Usage:
458 with log.section("somename"):
459 some test code
460
461 Args:
462 marker: The name of the nested section.
Stephen Warren83357fd2016-02-03 16:46:34 -0700463 anchor: The anchor value to pass to start_section().
Stephen Warrend2015062016-01-15 11:15:24 -0700464
465 Returns:
466 A context manager object.
Stephen Warrene8debf32016-01-26 13:41:30 -0700467 """
Stephen Warrend2015062016-01-15 11:15:24 -0700468
Stephen Warren83357fd2016-02-03 16:46:34 -0700469 return SectionCtxMgr(self, marker, anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700470
471 def error(self, msg):
Stephen Warrene8debf32016-01-26 13:41:30 -0700472 """Write an error note to the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700473
474 Args:
475 msg: A message describing the error.
476
477 Returns:
478 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700479 """
Stephen Warrend2015062016-01-15 11:15:24 -0700480
481 self._note("error", msg)
482
483 def warning(self, msg):
Stephen Warrene8debf32016-01-26 13:41:30 -0700484 """Write an warning note to the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700485
486 Args:
487 msg: A message describing the warning.
488
489 Returns:
490 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700491 """
Stephen Warrend2015062016-01-15 11:15:24 -0700492
Stephen Warren32090e52018-02-20 12:51:55 -0700493 self.seen_warning = True
Stephen Warrend2015062016-01-15 11:15:24 -0700494 self._note("warning", msg)
495
Stephen Warren32090e52018-02-20 12:51:55 -0700496 def get_and_reset_warning(self):
497 """Get and reset the log warning flag.
498
499 Args:
500 None
501
502 Returns:
503 Whether a warning was seen since the last call.
504 """
505
506 ret = self.seen_warning
507 self.seen_warning = False
508 return ret
509
Stephen Warrend2015062016-01-15 11:15:24 -0700510 def info(self, msg):
Stephen Warrene8debf32016-01-26 13:41:30 -0700511 """Write an informational note to the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700512
513 Args:
514 msg: An informational message.
515
516 Returns:
517 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700518 """
Stephen Warrend2015062016-01-15 11:15:24 -0700519
520 self._note("info", msg)
521
522 def action(self, msg):
Stephen Warrene8debf32016-01-26 13:41:30 -0700523 """Write an action note to the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700524
525 Args:
526 msg: A message describing the action that is being logged.
527
528 Returns:
529 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700530 """
Stephen Warrend2015062016-01-15 11:15:24 -0700531
532 self._note("action", msg)
533
Stephen Warren9679d332017-10-27 11:04:08 -0600534 def _get_time(self):
535 return datetime.datetime.now()
536
537 def timestamp(self):
538 """Write a timestamp to the log file.
539
540 Args:
541 None
542
543 Returns:
544 Nothing.
545 """
546
547 timestamp_now = self._get_time()
548 delta_prev = timestamp_now - self.timestamp_prev
549 delta_start = timestamp_now - self.timestamp_start
550 self.timestamp_prev = timestamp_now
551
552 self._note("timestamp",
553 "TIME: NOW: " + timestamp_now.strftime("%Y/%m/%d %H:%M:%S.%f"))
554 self._note("timestamp",
555 "TIME: SINCE-PREV: " + str(delta_prev))
556 self._note("timestamp",
557 "TIME: SINCE-START: " + str(delta_start))
558
Stephen Warren83357fd2016-02-03 16:46:34 -0700559 def status_pass(self, msg, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700560 """Write a note to the log file describing test(s) which passed.
Stephen Warrend2015062016-01-15 11:15:24 -0700561
562 Args:
Stephen Warren78b39cc2016-01-27 23:57:51 -0700563 msg: A message describing the passed test(s).
Stephen Warren83357fd2016-02-03 16:46:34 -0700564 anchor: Optional internal link target.
Stephen Warrend2015062016-01-15 11:15:24 -0700565
566 Returns:
567 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700568 """
Stephen Warrend2015062016-01-15 11:15:24 -0700569
Stephen Warren83357fd2016-02-03 16:46:34 -0700570 self._note("status-pass", msg, anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700571
Stephen Warren32090e52018-02-20 12:51:55 -0700572 def status_warning(self, msg, anchor=None):
573 """Write a note to the log file describing test(s) which passed.
574
575 Args:
576 msg: A message describing the passed test(s).
577 anchor: Optional internal link target.
578
579 Returns:
580 Nothing.
581 """
582
583 self._note("status-warning", msg, anchor)
584
Stephen Warren83357fd2016-02-03 16:46:34 -0700585 def status_skipped(self, msg, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700586 """Write a note to the log file describing skipped test(s).
Stephen Warrend2015062016-01-15 11:15:24 -0700587
588 Args:
Stephen Warren78b39cc2016-01-27 23:57:51 -0700589 msg: A message describing the skipped test(s).
Stephen Warren83357fd2016-02-03 16:46:34 -0700590 anchor: Optional internal link target.
Stephen Warrend2015062016-01-15 11:15:24 -0700591
592 Returns:
593 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700594 """
Stephen Warrend2015062016-01-15 11:15:24 -0700595
Stephen Warren83357fd2016-02-03 16:46:34 -0700596 self._note("status-skipped", msg, anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700597
Stephen Warren83357fd2016-02-03 16:46:34 -0700598 def status_xfail(self, msg, anchor=None):
Stephen Warren78b39cc2016-01-27 23:57:51 -0700599 """Write a note to the log file describing xfailed test(s).
600
601 Args:
602 msg: A message describing the xfailed test(s).
Stephen Warren83357fd2016-02-03 16:46:34 -0700603 anchor: Optional internal link target.
Stephen Warren78b39cc2016-01-27 23:57:51 -0700604
605 Returns:
606 Nothing.
607 """
608
Stephen Warren83357fd2016-02-03 16:46:34 -0700609 self._note("status-xfail", msg, anchor)
Stephen Warren78b39cc2016-01-27 23:57:51 -0700610
Stephen Warren83357fd2016-02-03 16:46:34 -0700611 def status_xpass(self, msg, anchor=None):
Stephen Warren78b39cc2016-01-27 23:57:51 -0700612 """Write a note to the log file describing xpassed test(s).
613
614 Args:
615 msg: A message describing the xpassed test(s).
Stephen Warren83357fd2016-02-03 16:46:34 -0700616 anchor: Optional internal link target.
Stephen Warren78b39cc2016-01-27 23:57:51 -0700617
618 Returns:
619 Nothing.
620 """
621
Stephen Warren83357fd2016-02-03 16:46:34 -0700622 self._note("status-xpass", msg, anchor)
Stephen Warren78b39cc2016-01-27 23:57:51 -0700623
Stephen Warren83357fd2016-02-03 16:46:34 -0700624 def status_fail(self, msg, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700625 """Write a note to the log file describing failed test(s).
Stephen Warrend2015062016-01-15 11:15:24 -0700626
627 Args:
Stephen Warren78b39cc2016-01-27 23:57:51 -0700628 msg: A message describing the failed test(s).
Stephen Warren83357fd2016-02-03 16:46:34 -0700629 anchor: Optional internal link target.
Stephen Warrend2015062016-01-15 11:15:24 -0700630
631 Returns:
632 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700633 """
Stephen Warrend2015062016-01-15 11:15:24 -0700634
Stephen Warren83357fd2016-02-03 16:46:34 -0700635 self._note("status-fail", msg, anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700636
637 def get_stream(self, name, chained_file=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700638 """Create an object to log a single stream's data into the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700639
640 This creates a "file-like" object that can be written to in order to
641 write a single stream's data to the log file. The implementation will
642 handle any required interleaving of data (from multiple streams) in
643 the log, in a way that makes it obvious which stream each bit of data
644 came from.
645
646 Args:
647 name: The name of the stream.
648 chained_file: The file-like object to which all stream data should
649 be logged to in addition to this log. Can be None.
650
651 Returns:
652 A file-like object.
Stephen Warrene8debf32016-01-26 13:41:30 -0700653 """
Stephen Warrend2015062016-01-15 11:15:24 -0700654
655 return LogfileStream(self, name, chained_file)
656
657 def get_runner(self, name, chained_file=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700658 """Create an object that executes processes and logs their output.
Stephen Warrend2015062016-01-15 11:15:24 -0700659
660 Args:
661 name: The name of this sub-process.
662 chained_file: The file-like object to which all stream data should
663 be logged to in addition to logfile. Can be None.
664
665 Returns:
666 A RunAndLog object.
Stephen Warrene8debf32016-01-26 13:41:30 -0700667 """
Stephen Warrend2015062016-01-15 11:15:24 -0700668
669 return RunAndLog(self, name, chained_file)
670
671 def write(self, stream, data, implicit=False):
Stephen Warrene8debf32016-01-26 13:41:30 -0700672 """Write stream data into the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700673
674 This function should only be used by instances of LogfileStream or
675 RunAndLog.
676
677 Args:
678 stream: The stream whose data is being logged.
679 data: The data to log.
680 implicit: Boolean indicating whether data actually appeared in the
681 stream, or was implicitly generated. A valid use-case is to
682 repeat a shell prompt at the start of each separate log
683 section, which makes the log sections more readable in
684 isolation.
685
686 Returns:
687 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700688 """
Stephen Warrend2015062016-01-15 11:15:24 -0700689
690 if stream != self.last_stream:
691 self._terminate_stream()
Stephen Warren83357fd2016-02-03 16:46:34 -0700692 self.f.write('<div class="stream block">\n')
693 self.f.write('<div class="stream-header block-header">Stream: ' +
694 stream.name + '</div>\n')
695 self.f.write('<div class="stream-content block-content">\n')
Stephen Warrena2ec5602016-01-26 13:41:31 -0700696 self.f.write('<pre>')
Stephen Warrend2015062016-01-15 11:15:24 -0700697 if implicit:
Stephen Warrena2ec5602016-01-26 13:41:31 -0700698 self.f.write('<span class="implicit">')
Stephen Warrend2015062016-01-15 11:15:24 -0700699 self.f.write(self._escape(data))
700 if implicit:
Stephen Warrena2ec5602016-01-26 13:41:31 -0700701 self.f.write('</span>')
Stephen Warrend2015062016-01-15 11:15:24 -0700702 self.last_stream = stream
703
704 def flush(self):
Stephen Warrene8debf32016-01-26 13:41:30 -0700705 """Flush the log stream, to ensure correct log interleaving.
Stephen Warrend2015062016-01-15 11:15:24 -0700706
707 Args:
708 None.
709
710 Returns:
711 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700712 """
Stephen Warrend2015062016-01-15 11:15:24 -0700713
714 self.f.flush()