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