blob: 545a7743022335ba5d489955ec6875bbb2914bcf [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
Stephen Warren3f2faf72016-01-22 12:30:11 -0700112 def run(self, cmd, cwd=None, ignore_errors=False):
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.
Stephen Warrend2015062016-01-15 11:15:24 -0700126
127 Returns:
Simon Glass3b8d9d92016-07-03 09:40:37 -0600128 The output as a string.
Stephen Warrene8debf32016-01-26 13:41:30 -0700129 """
Stephen Warrend2015062016-01-15 11:15:24 -0700130
Stephen Warrena2ec5602016-01-26 13:41:31 -0700131 msg = '+' + ' '.join(cmd) + '\n'
Stephen Warrend2015062016-01-15 11:15:24 -0700132 if self.chained_file:
133 self.chained_file.write(msg)
134 self.logfile.write(self, msg)
135
136 try:
137 p = subprocess.Popen(cmd, cwd=cwd,
138 stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
139 (stdout, stderr) = p.communicate()
Tom Rinifd31fc12019-10-24 11:59:21 -0400140 if stdout is not None:
141 stdout = stdout.decode('utf-8')
142 if stderr is not None:
143 stderr = stderr.decode('utf-8')
Stephen Warrend2015062016-01-15 11:15:24 -0700144 output = ''
145 if stdout:
146 if stderr:
147 output += 'stdout:\n'
148 output += stdout
149 if stderr:
150 if stdout:
151 output += 'stderr:\n'
152 output += stderr
153 exit_status = p.returncode
154 exception = None
155 except subprocess.CalledProcessError as cpe:
156 output = cpe.output
157 exit_status = cpe.returncode
158 exception = cpe
159 except Exception as e:
160 output = ''
161 exit_status = 0
162 exception = e
163 if output and not output.endswith('\n'):
164 output += '\n'
Stephen Warren3f2faf72016-01-22 12:30:11 -0700165 if exit_status and not exception and not ignore_errors:
Stephen Warrend2015062016-01-15 11:15:24 -0700166 exception = Exception('Exit code: ' + str(exit_status))
167 if exception:
168 output += str(exception) + '\n'
169 self.logfile.write(self, output)
170 if self.chained_file:
171 self.chained_file.write(output)
Stephen Warren9679d332017-10-27 11:04:08 -0600172 self.logfile.timestamp()
Simon Glass86845bf2016-07-03 09:40:38 -0600173
174 # Store the output so it can be accessed if we raise an exception.
175 self.output = output
Simon Glass7f64b182016-07-31 17:35:03 -0600176 self.exit_status = exit_status
Stephen Warrend2015062016-01-15 11:15:24 -0700177 if exception:
178 raise exception
Simon Glass3b8d9d92016-07-03 09:40:37 -0600179 return output
Stephen Warrend2015062016-01-15 11:15:24 -0700180
181class SectionCtxMgr(object):
Stephen Warrene8debf32016-01-26 13:41:30 -0700182 """A context manager for Python's "with" statement, which allows a certain
Stephen Warrend2015062016-01-15 11:15:24 -0700183 portion of test code to be logged to a separate section of the log file.
184 Objects of this type should be created by factory functions in the Logfile
Stephen Warrene8debf32016-01-26 13:41:30 -0700185 class rather than directly."""
Stephen Warrend2015062016-01-15 11:15:24 -0700186
Stephen Warren83357fd2016-02-03 16:46:34 -0700187 def __init__(self, log, marker, anchor):
Stephen Warrene8debf32016-01-26 13:41:30 -0700188 """Initialize a new object.
Stephen Warrend2015062016-01-15 11:15:24 -0700189
190 Args:
191 log: The Logfile object to log to.
192 marker: The name of the nested log section.
Stephen Warren83357fd2016-02-03 16:46:34 -0700193 anchor: The anchor value to pass to start_section().
Stephen Warrend2015062016-01-15 11:15:24 -0700194
195 Returns:
196 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700197 """
Stephen Warrend2015062016-01-15 11:15:24 -0700198
199 self.log = log
200 self.marker = marker
Stephen Warren83357fd2016-02-03 16:46:34 -0700201 self.anchor = anchor
Stephen Warrend2015062016-01-15 11:15:24 -0700202
203 def __enter__(self):
Stephen Warren83357fd2016-02-03 16:46:34 -0700204 self.anchor = self.log.start_section(self.marker, self.anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700205
206 def __exit__(self, extype, value, traceback):
207 self.log.end_section(self.marker)
208
209class Logfile(object):
Stephen Warrene8debf32016-01-26 13:41:30 -0700210 """Generates an HTML-formatted log file containing multiple streams of
211 data, each represented in a well-delineated/-structured fashion."""
Stephen Warrend2015062016-01-15 11:15:24 -0700212
213 def __init__(self, fn):
Stephen Warrene8debf32016-01-26 13:41:30 -0700214 """Initialize a new object.
Stephen Warrend2015062016-01-15 11:15:24 -0700215
216 Args:
217 fn: The filename to write to.
218
219 Returns:
220 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700221 """
Stephen Warrend2015062016-01-15 11:15:24 -0700222
Tom Rinifd31fc12019-10-24 11:59:21 -0400223 self.f = open(fn, 'wt', encoding='utf-8')
Stephen Warrend2015062016-01-15 11:15:24 -0700224 self.last_stream = None
225 self.blocks = []
226 self.cur_evt = 1
Stephen Warren83357fd2016-02-03 16:46:34 -0700227 self.anchor = 0
Stephen Warren9679d332017-10-27 11:04:08 -0600228 self.timestamp_start = self._get_time()
229 self.timestamp_prev = self.timestamp_start
230 self.timestamp_blocks = []
Stephen Warren32090e52018-02-20 12:51:55 -0700231 self.seen_warning = False
Stephen Warren83357fd2016-02-03 16:46:34 -0700232
Stephen Warrena2ec5602016-01-26 13:41:31 -0700233 shutil.copy(mod_dir + '/multiplexed_log.css', os.path.dirname(fn))
234 self.f.write('''\
Stephen Warrend2015062016-01-15 11:15:24 -0700235<html>
236<head>
237<link rel="stylesheet" type="text/css" href="multiplexed_log.css">
Stephen Warren83357fd2016-02-03 16:46:34 -0700238<script src="http://code.jquery.com/jquery.min.js"></script>
239<script>
240$(document).ready(function () {
241 // Copy status report HTML to start of log for easy access
242 sts = $(".block#status_report")[0].outerHTML;
243 $("tt").prepend(sts);
244
245 // Add expand/contract buttons to all block headers
246 btns = "<span class=\\\"block-expand hidden\\\">[+] </span>" +
247 "<span class=\\\"block-contract\\\">[-] </span>";
248 $(".block-header").prepend(btns);
249
250 // Pre-contract all blocks which passed, leaving only problem cases
251 // expanded, to highlight issues the user should look at.
252 // Only top-level blocks (sections) should have any status
253 passed_bcs = $(".block-content:has(.status-pass)");
254 // Some blocks might have multiple status entries (e.g. the status
255 // report), so take care not to hide blocks with partial success.
256 passed_bcs = passed_bcs.not(":has(.status-fail)");
257 passed_bcs = passed_bcs.not(":has(.status-xfail)");
258 passed_bcs = passed_bcs.not(":has(.status-xpass)");
259 passed_bcs = passed_bcs.not(":has(.status-skipped)");
Stephen Warren32090e52018-02-20 12:51:55 -0700260 passed_bcs = passed_bcs.not(":has(.status-warning)");
Stephen Warren83357fd2016-02-03 16:46:34 -0700261 // Hide the passed blocks
262 passed_bcs.addClass("hidden");
263 // Flip the expand/contract button hiding for those blocks.
264 bhs = passed_bcs.parent().children(".block-header")
265 bhs.children(".block-expand").removeClass("hidden");
266 bhs.children(".block-contract").addClass("hidden");
267
268 // Add click handler to block headers.
269 // The handler expands/contracts the block.
270 $(".block-header").on("click", function (e) {
271 var header = $(this);
272 var content = header.next(".block-content");
273 var expanded = !content.hasClass("hidden");
274 if (expanded) {
275 content.addClass("hidden");
276 header.children(".block-expand").first().removeClass("hidden");
277 header.children(".block-contract").first().addClass("hidden");
278 } else {
279 header.children(".block-contract").first().removeClass("hidden");
280 header.children(".block-expand").first().addClass("hidden");
281 content.removeClass("hidden");
282 }
283 });
284
285 // When clicking on a link, expand the target block
286 $("a").on("click", function (e) {
287 var block = $($(this).attr("href"));
288 var header = block.children(".block-header");
289 var content = block.children(".block-content").first();
290 header.children(".block-contract").first().removeClass("hidden");
291 header.children(".block-expand").first().addClass("hidden");
292 content.removeClass("hidden");
293 });
294});
295</script>
Stephen Warrend2015062016-01-15 11:15:24 -0700296</head>
297<body>
298<tt>
Stephen Warrena2ec5602016-01-26 13:41:31 -0700299''')
Stephen Warrend2015062016-01-15 11:15:24 -0700300
301 def close(self):
Stephen Warrene8debf32016-01-26 13:41:30 -0700302 """Close the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700303
304 After calling this function, no more data may be written to the log.
305
306 Args:
307 None.
308
309 Returns:
310 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700311 """
Stephen Warrend2015062016-01-15 11:15:24 -0700312
Stephen Warrena2ec5602016-01-26 13:41:31 -0700313 self.f.write('''\
Stephen Warrend2015062016-01-15 11:15:24 -0700314</tt>
315</body>
316</html>
Stephen Warrena2ec5602016-01-26 13:41:31 -0700317''')
Stephen Warrend2015062016-01-15 11:15:24 -0700318 self.f.close()
319
320 # The set of characters that should be represented as hexadecimal codes in
321 # the log file.
Simon Glass87b05ee2018-10-01 21:12:34 -0600322 _nonprint = {ord('%')}
323 _nonprint.update({c for c in range(0, 32) if c not in (9, 10)})
324 _nonprint.update({c for c in range(127, 256)})
Stephen Warrend2015062016-01-15 11:15:24 -0700325
326 def _escape(self, data):
Stephen Warrene8debf32016-01-26 13:41:30 -0700327 """Render data format suitable for inclusion in an HTML document.
Stephen Warrend2015062016-01-15 11:15:24 -0700328
329 This includes HTML-escaping certain characters, and translating
330 control characters to a hexadecimal representation.
331
332 Args:
333 data: The raw string data to be escaped.
334
335 Returns:
336 An escaped version of the data.
Stephen Warrene8debf32016-01-26 13:41:30 -0700337 """
Stephen Warrend2015062016-01-15 11:15:24 -0700338
Stephen Warrena2ec5602016-01-26 13:41:31 -0700339 data = data.replace(chr(13), '')
Simon Glass87b05ee2018-10-01 21:12:34 -0600340 data = ''.join((ord(c) in self._nonprint) and ('%%%02x' % ord(c)) or
Stephen Warrend2015062016-01-15 11:15:24 -0700341 c for c in data)
Tom Rinife1193e2019-10-24 11:59:20 -0400342 data = html.escape(data)
Stephen Warrend2015062016-01-15 11:15:24 -0700343 return data
344
345 def _terminate_stream(self):
Stephen Warrene8debf32016-01-26 13:41:30 -0700346 """Write HTML to the log file to terminate the current stream's data.
Stephen Warrend2015062016-01-15 11:15:24 -0700347
348 Args:
349 None.
350
351 Returns:
352 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700353 """
Stephen Warrend2015062016-01-15 11:15:24 -0700354
355 self.cur_evt += 1
356 if not self.last_stream:
357 return
Stephen Warrena2ec5602016-01-26 13:41:31 -0700358 self.f.write('</pre>\n')
Stephen Warren83357fd2016-02-03 16:46:34 -0700359 self.f.write('<div class="stream-trailer block-trailer">End stream: ' +
Stephen Warrena2ec5602016-01-26 13:41:31 -0700360 self.last_stream.name + '</div>\n')
361 self.f.write('</div>\n')
Stephen Warren83357fd2016-02-03 16:46:34 -0700362 self.f.write('</div>\n')
Stephen Warrend2015062016-01-15 11:15:24 -0700363 self.last_stream = None
364
Stephen Warren83357fd2016-02-03 16:46:34 -0700365 def _note(self, note_type, msg, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700366 """Write a note or one-off message to the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700367
368 Args:
369 note_type: The type of note. This must be a value supported by the
370 accompanying multiplexed_log.css.
371 msg: The note/message to log.
Stephen Warren83357fd2016-02-03 16:46:34 -0700372 anchor: Optional internal link target.
Stephen Warrend2015062016-01-15 11:15:24 -0700373
374 Returns:
375 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700376 """
Stephen Warrend2015062016-01-15 11:15:24 -0700377
378 self._terminate_stream()
Stephen Warren83357fd2016-02-03 16:46:34 -0700379 self.f.write('<div class="' + note_type + '">\n')
Stephen Warren83357fd2016-02-03 16:46:34 -0700380 self.f.write('<pre>')
Stephen Warren83357fd2016-02-03 16:46:34 -0700381 if anchor:
Stephen Warren117eeb72017-09-18 11:50:47 -0600382 self.f.write('<a href="#%s">' % anchor)
383 self.f.write(self._escape(msg))
384 if anchor:
385 self.f.write('</a>')
386 self.f.write('\n</pre>\n')
Stephen Warren83357fd2016-02-03 16:46:34 -0700387 self.f.write('</div>\n')
Stephen Warrend2015062016-01-15 11:15:24 -0700388
Stephen Warren83357fd2016-02-03 16:46:34 -0700389 def start_section(self, marker, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700390 """Begin a new nested section in the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700391
392 Args:
393 marker: The name of the section that is starting.
Stephen Warren83357fd2016-02-03 16:46:34 -0700394 anchor: The value to use for the anchor. If None, a unique value
395 will be calculated and used
Stephen Warrend2015062016-01-15 11:15:24 -0700396
397 Returns:
Stephen Warren83357fd2016-02-03 16:46:34 -0700398 Name of the HTML anchor emitted before section.
Stephen Warrene8debf32016-01-26 13:41:30 -0700399 """
Stephen Warrend2015062016-01-15 11:15:24 -0700400
401 self._terminate_stream()
402 self.blocks.append(marker)
Stephen Warren9679d332017-10-27 11:04:08 -0600403 self.timestamp_blocks.append(self._get_time())
Stephen Warren83357fd2016-02-03 16:46:34 -0700404 if not anchor:
405 self.anchor += 1
406 anchor = str(self.anchor)
Stephen Warrena2ec5602016-01-26 13:41:31 -0700407 blk_path = '/'.join(self.blocks)
Stephen Warren83357fd2016-02-03 16:46:34 -0700408 self.f.write('<div class="section block" id="' + anchor + '">\n')
409 self.f.write('<div class="section-header block-header">Section: ' +
410 blk_path + '</div>\n')
411 self.f.write('<div class="section-content block-content">\n')
Stephen Warren9679d332017-10-27 11:04:08 -0600412 self.timestamp()
Stephen Warren83357fd2016-02-03 16:46:34 -0700413
414 return anchor
Stephen Warrend2015062016-01-15 11:15:24 -0700415
416 def end_section(self, marker):
Stephen Warrene8debf32016-01-26 13:41:30 -0700417 """Terminate the current nested section in the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700418
419 This function validates proper nesting of start_section() and
420 end_section() calls. If a mismatch is found, an exception is raised.
421
422 Args:
423 marker: The name of the section that is ending.
424
425 Returns:
426 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700427 """
Stephen Warrend2015062016-01-15 11:15:24 -0700428
429 if (not self.blocks) or (marker != self.blocks[-1]):
Stephen Warrena2ec5602016-01-26 13:41:31 -0700430 raise Exception('Block nesting mismatch: "%s" "%s"' %
431 (marker, '/'.join(self.blocks)))
Stephen Warrend2015062016-01-15 11:15:24 -0700432 self._terminate_stream()
Stephen Warren9679d332017-10-27 11:04:08 -0600433 timestamp_now = self._get_time()
434 timestamp_section_start = self.timestamp_blocks.pop()
435 delta_section = timestamp_now - timestamp_section_start
436 self._note("timestamp",
437 "TIME: SINCE-SECTION: " + str(delta_section))
Stephen Warrena2ec5602016-01-26 13:41:31 -0700438 blk_path = '/'.join(self.blocks)
Stephen Warren83357fd2016-02-03 16:46:34 -0700439 self.f.write('<div class="section-trailer block-trailer">' +
440 'End section: ' + blk_path + '</div>\n')
441 self.f.write('</div>\n')
Stephen Warrena2ec5602016-01-26 13:41:31 -0700442 self.f.write('</div>\n')
Stephen Warrend2015062016-01-15 11:15:24 -0700443 self.blocks.pop()
444
Stephen Warren83357fd2016-02-03 16:46:34 -0700445 def section(self, marker, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700446 """Create a temporary section in the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700447
448 This function creates a context manager for Python's "with" statement,
449 which allows a certain portion of test code to be logged to a separate
450 section of the log file.
451
452 Usage:
453 with log.section("somename"):
454 some test code
455
456 Args:
457 marker: The name of the nested section.
Stephen Warren83357fd2016-02-03 16:46:34 -0700458 anchor: The anchor value to pass to start_section().
Stephen Warrend2015062016-01-15 11:15:24 -0700459
460 Returns:
461 A context manager object.
Stephen Warrene8debf32016-01-26 13:41:30 -0700462 """
Stephen Warrend2015062016-01-15 11:15:24 -0700463
Stephen Warren83357fd2016-02-03 16:46:34 -0700464 return SectionCtxMgr(self, marker, anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700465
466 def error(self, msg):
Stephen Warrene8debf32016-01-26 13:41:30 -0700467 """Write an error note to the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700468
469 Args:
470 msg: A message describing the error.
471
472 Returns:
473 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700474 """
Stephen Warrend2015062016-01-15 11:15:24 -0700475
476 self._note("error", msg)
477
478 def warning(self, msg):
Stephen Warrene8debf32016-01-26 13:41:30 -0700479 """Write an warning note to the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700480
481 Args:
482 msg: A message describing the warning.
483
484 Returns:
485 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700486 """
Stephen Warrend2015062016-01-15 11:15:24 -0700487
Stephen Warren32090e52018-02-20 12:51:55 -0700488 self.seen_warning = True
Stephen Warrend2015062016-01-15 11:15:24 -0700489 self._note("warning", msg)
490
Stephen Warren32090e52018-02-20 12:51:55 -0700491 def get_and_reset_warning(self):
492 """Get and reset the log warning flag.
493
494 Args:
495 None
496
497 Returns:
498 Whether a warning was seen since the last call.
499 """
500
501 ret = self.seen_warning
502 self.seen_warning = False
503 return ret
504
Stephen Warrend2015062016-01-15 11:15:24 -0700505 def info(self, msg):
Stephen Warrene8debf32016-01-26 13:41:30 -0700506 """Write an informational note to the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700507
508 Args:
509 msg: An informational message.
510
511 Returns:
512 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700513 """
Stephen Warrend2015062016-01-15 11:15:24 -0700514
515 self._note("info", msg)
516
517 def action(self, msg):
Stephen Warrene8debf32016-01-26 13:41:30 -0700518 """Write an action note to the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700519
520 Args:
521 msg: A message describing the action that is being logged.
522
523 Returns:
524 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700525 """
Stephen Warrend2015062016-01-15 11:15:24 -0700526
527 self._note("action", msg)
528
Stephen Warren9679d332017-10-27 11:04:08 -0600529 def _get_time(self):
530 return datetime.datetime.now()
531
532 def timestamp(self):
533 """Write a timestamp to the log file.
534
535 Args:
536 None
537
538 Returns:
539 Nothing.
540 """
541
542 timestamp_now = self._get_time()
543 delta_prev = timestamp_now - self.timestamp_prev
544 delta_start = timestamp_now - self.timestamp_start
545 self.timestamp_prev = timestamp_now
546
547 self._note("timestamp",
548 "TIME: NOW: " + timestamp_now.strftime("%Y/%m/%d %H:%M:%S.%f"))
549 self._note("timestamp",
550 "TIME: SINCE-PREV: " + str(delta_prev))
551 self._note("timestamp",
552 "TIME: SINCE-START: " + str(delta_start))
553
Stephen Warren83357fd2016-02-03 16:46:34 -0700554 def status_pass(self, msg, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700555 """Write a note to the log file describing test(s) which passed.
Stephen Warrend2015062016-01-15 11:15:24 -0700556
557 Args:
Stephen Warren78b39cc2016-01-27 23:57:51 -0700558 msg: A message describing the passed test(s).
Stephen Warren83357fd2016-02-03 16:46:34 -0700559 anchor: Optional internal link target.
Stephen Warrend2015062016-01-15 11:15:24 -0700560
561 Returns:
562 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700563 """
Stephen Warrend2015062016-01-15 11:15:24 -0700564
Stephen Warren83357fd2016-02-03 16:46:34 -0700565 self._note("status-pass", msg, anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700566
Stephen Warren32090e52018-02-20 12:51:55 -0700567 def status_warning(self, msg, anchor=None):
568 """Write a note to the log file describing test(s) which passed.
569
570 Args:
571 msg: A message describing the passed test(s).
572 anchor: Optional internal link target.
573
574 Returns:
575 Nothing.
576 """
577
578 self._note("status-warning", msg, anchor)
579
Stephen Warren83357fd2016-02-03 16:46:34 -0700580 def status_skipped(self, msg, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700581 """Write a note to the log file describing skipped test(s).
Stephen Warrend2015062016-01-15 11:15:24 -0700582
583 Args:
Stephen Warren78b39cc2016-01-27 23:57:51 -0700584 msg: A message describing the skipped test(s).
Stephen Warren83357fd2016-02-03 16:46:34 -0700585 anchor: Optional internal link target.
Stephen Warrend2015062016-01-15 11:15:24 -0700586
587 Returns:
588 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700589 """
Stephen Warrend2015062016-01-15 11:15:24 -0700590
Stephen Warren83357fd2016-02-03 16:46:34 -0700591 self._note("status-skipped", msg, anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700592
Stephen Warren83357fd2016-02-03 16:46:34 -0700593 def status_xfail(self, msg, anchor=None):
Stephen Warren78b39cc2016-01-27 23:57:51 -0700594 """Write a note to the log file describing xfailed test(s).
595
596 Args:
597 msg: A message describing the xfailed test(s).
Stephen Warren83357fd2016-02-03 16:46:34 -0700598 anchor: Optional internal link target.
Stephen Warren78b39cc2016-01-27 23:57:51 -0700599
600 Returns:
601 Nothing.
602 """
603
Stephen Warren83357fd2016-02-03 16:46:34 -0700604 self._note("status-xfail", msg, anchor)
Stephen Warren78b39cc2016-01-27 23:57:51 -0700605
Stephen Warren83357fd2016-02-03 16:46:34 -0700606 def status_xpass(self, msg, anchor=None):
Stephen Warren78b39cc2016-01-27 23:57:51 -0700607 """Write a note to the log file describing xpassed test(s).
608
609 Args:
610 msg: A message describing the xpassed test(s).
Stephen Warren83357fd2016-02-03 16:46:34 -0700611 anchor: Optional internal link target.
Stephen Warren78b39cc2016-01-27 23:57:51 -0700612
613 Returns:
614 Nothing.
615 """
616
Stephen Warren83357fd2016-02-03 16:46:34 -0700617 self._note("status-xpass", msg, anchor)
Stephen Warren78b39cc2016-01-27 23:57:51 -0700618
Stephen Warren83357fd2016-02-03 16:46:34 -0700619 def status_fail(self, msg, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700620 """Write a note to the log file describing failed test(s).
Stephen Warrend2015062016-01-15 11:15:24 -0700621
622 Args:
Stephen Warren78b39cc2016-01-27 23:57:51 -0700623 msg: A message describing the failed test(s).
Stephen Warren83357fd2016-02-03 16:46:34 -0700624 anchor: Optional internal link target.
Stephen Warrend2015062016-01-15 11:15:24 -0700625
626 Returns:
627 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700628 """
Stephen Warrend2015062016-01-15 11:15:24 -0700629
Stephen Warren83357fd2016-02-03 16:46:34 -0700630 self._note("status-fail", msg, anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700631
632 def get_stream(self, name, chained_file=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700633 """Create an object to log a single stream's data into the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700634
635 This creates a "file-like" object that can be written to in order to
636 write a single stream's data to the log file. The implementation will
637 handle any required interleaving of data (from multiple streams) in
638 the log, in a way that makes it obvious which stream each bit of data
639 came from.
640
641 Args:
642 name: The name of the stream.
643 chained_file: The file-like object to which all stream data should
644 be logged to in addition to this log. Can be None.
645
646 Returns:
647 A file-like object.
Stephen Warrene8debf32016-01-26 13:41:30 -0700648 """
Stephen Warrend2015062016-01-15 11:15:24 -0700649
650 return LogfileStream(self, name, chained_file)
651
652 def get_runner(self, name, chained_file=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700653 """Create an object that executes processes and logs their output.
Stephen Warrend2015062016-01-15 11:15:24 -0700654
655 Args:
656 name: The name of this sub-process.
657 chained_file: The file-like object to which all stream data should
658 be logged to in addition to logfile. Can be None.
659
660 Returns:
661 A RunAndLog object.
Stephen Warrene8debf32016-01-26 13:41:30 -0700662 """
Stephen Warrend2015062016-01-15 11:15:24 -0700663
664 return RunAndLog(self, name, chained_file)
665
666 def write(self, stream, data, implicit=False):
Stephen Warrene8debf32016-01-26 13:41:30 -0700667 """Write stream data into the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700668
669 This function should only be used by instances of LogfileStream or
670 RunAndLog.
671
672 Args:
673 stream: The stream whose data is being logged.
674 data: The data to log.
675 implicit: Boolean indicating whether data actually appeared in the
676 stream, or was implicitly generated. A valid use-case is to
677 repeat a shell prompt at the start of each separate log
678 section, which makes the log sections more readable in
679 isolation.
680
681 Returns:
682 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700683 """
Stephen Warrend2015062016-01-15 11:15:24 -0700684
685 if stream != self.last_stream:
686 self._terminate_stream()
Stephen Warren83357fd2016-02-03 16:46:34 -0700687 self.f.write('<div class="stream block">\n')
688 self.f.write('<div class="stream-header block-header">Stream: ' +
689 stream.name + '</div>\n')
690 self.f.write('<div class="stream-content block-content">\n')
Stephen Warrena2ec5602016-01-26 13:41:31 -0700691 self.f.write('<pre>')
Stephen Warrend2015062016-01-15 11:15:24 -0700692 if implicit:
Stephen Warrena2ec5602016-01-26 13:41:31 -0700693 self.f.write('<span class="implicit">')
Stephen Warrend2015062016-01-15 11:15:24 -0700694 self.f.write(self._escape(data))
695 if implicit:
Stephen Warrena2ec5602016-01-26 13:41:31 -0700696 self.f.write('</span>')
Stephen Warrend2015062016-01-15 11:15:24 -0700697 self.last_stream = stream
698
699 def flush(self):
Stephen Warrene8debf32016-01-26 13:41:30 -0700700 """Flush the log stream, to ensure correct log interleaving.
Stephen Warrend2015062016-01-15 11:15:24 -0700701
702 Args:
703 None.
704
705 Returns:
706 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700707 """
Stephen Warrend2015062016-01-15 11:15:24 -0700708
709 self.f.flush()