blob: a2cfd71746190f9e54cfc8f8f2ba40132be80e1d [file] [log] [blame]
Stephen Warren10e50632016-01-15 11:15:24 -07001# Copyright (c) 2015 Stephen Warren
2# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
3#
4# SPDX-License-Identifier: GPL-2.0
5
6# Generate an HTML-formatted log file containing multiple streams of data,
7# each represented in a well-delineated/-structured fashion.
8
9import cgi
Stephen Warrenb1c556a2017-10-27 11:04:08 -060010import datetime
Stephen Warren10e50632016-01-15 11:15:24 -070011import os.path
12import shutil
13import subprocess
14
15mod_dir = os.path.dirname(os.path.abspath(__file__))
16
17class LogfileStream(object):
Stephen Warren75e731e2016-01-26 13:41:30 -070018 """A file-like object used to write a single logical stream of data into
Stephen Warren10e50632016-01-15 11:15:24 -070019 a multiplexed log file. Objects of this type should be created by factory
Stephen Warren75e731e2016-01-26 13:41:30 -070020 functions in the Logfile class rather than directly."""
Stephen Warren10e50632016-01-15 11:15:24 -070021
22 def __init__(self, logfile, name, chained_file):
Stephen Warren75e731e2016-01-26 13:41:30 -070023 """Initialize a new object.
Stephen Warren10e50632016-01-15 11:15:24 -070024
25 Args:
26 logfile: The Logfile object to log to.
27 name: The name of this log stream.
28 chained_file: The file-like object to which all stream data should be
29 logged to in addition to logfile. Can be None.
30
31 Returns:
32 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -070033 """
Stephen Warren10e50632016-01-15 11:15:24 -070034
35 self.logfile = logfile
36 self.name = name
37 self.chained_file = chained_file
38
39 def close(self):
Stephen Warren75e731e2016-01-26 13:41:30 -070040 """Dummy function so that this class is "file-like".
Stephen Warren10e50632016-01-15 11:15:24 -070041
42 Args:
43 None.
44
45 Returns:
46 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -070047 """
Stephen Warren10e50632016-01-15 11:15:24 -070048
49 pass
50
51 def write(self, data, implicit=False):
Stephen Warren75e731e2016-01-26 13:41:30 -070052 """Write data to the log stream.
Stephen Warren10e50632016-01-15 11:15:24 -070053
54 Args:
55 data: The data to write tot he file.
56 implicit: Boolean indicating whether data actually appeared in the
57 stream, or was implicitly generated. A valid use-case is to
58 repeat a shell prompt at the start of each separate log
59 section, which makes the log sections more readable in
60 isolation.
61
62 Returns:
63 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -070064 """
Stephen Warren10e50632016-01-15 11:15:24 -070065
66 self.logfile.write(self, data, implicit)
67 if self.chained_file:
68 self.chained_file.write(data)
69
70 def flush(self):
Stephen Warren75e731e2016-01-26 13:41:30 -070071 """Flush the log stream, to ensure correct log interleaving.
Stephen Warren10e50632016-01-15 11:15:24 -070072
73 Args:
74 None.
75
76 Returns:
77 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -070078 """
Stephen Warren10e50632016-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 Warren75e731e2016-01-26 13:41:30 -070085 """A utility object used to execute sub-processes and log their output to
Stephen Warren10e50632016-01-15 11:15:24 -070086 a multiplexed log file. Objects of this type should be created by factory
Stephen Warren75e731e2016-01-26 13:41:30 -070087 functions in the Logfile class rather than directly."""
Stephen Warren10e50632016-01-15 11:15:24 -070088
89 def __init__(self, logfile, name, chained_file):
Stephen Warren75e731e2016-01-26 13:41:30 -070090 """Initialize a new object.
Stephen Warren10e50632016-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 Warren75e731e2016-01-26 13:41:30 -0700100 """
Stephen Warren10e50632016-01-15 11:15:24 -0700101
102 self.logfile = logfile
103 self.name = name
104 self.chained_file = chained_file
Simon Glass4134caf2016-07-03 09:40:38 -0600105 self.output = None
Simon Glass95adb802016-07-31 17:35:03 -0600106 self.exit_status = None
Stephen Warren10e50632016-01-15 11:15:24 -0700107
108 def close(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700109 """Clean up any resources managed by this object."""
Stephen Warren10e50632016-01-15 11:15:24 -0700110 pass
111
Stephen Warren118e37e2016-01-22 12:30:11 -0700112 def run(self, cmd, cwd=None, ignore_errors=False):
Stephen Warren75e731e2016-01-26 13:41:30 -0700113 """Run a command as a sub-process, and log the results.
Stephen Warren10e50632016-01-15 11:15:24 -0700114
Simon Glass4134caf2016-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 Warren10e50632016-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 Warren118e37e2016-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 Warren10e50632016-01-15 11:15:24 -0700126
127 Returns:
Simon Glass9f3c9e92016-07-03 09:40:37 -0600128 The output as a string.
Stephen Warren75e731e2016-01-26 13:41:30 -0700129 """
Stephen Warren10e50632016-01-15 11:15:24 -0700130
Stephen Warren3deb8962016-01-26 13:41:31 -0700131 msg = '+' + ' '.join(cmd) + '\n'
Stephen Warren10e50632016-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()
140 output = ''
141 if stdout:
142 if stderr:
143 output += 'stdout:\n'
144 output += stdout
145 if stderr:
146 if stdout:
147 output += 'stderr:\n'
148 output += stderr
149 exit_status = p.returncode
150 exception = None
151 except subprocess.CalledProcessError as cpe:
152 output = cpe.output
153 exit_status = cpe.returncode
154 exception = cpe
155 except Exception as e:
156 output = ''
157 exit_status = 0
158 exception = e
159 if output and not output.endswith('\n'):
160 output += '\n'
Stephen Warren118e37e2016-01-22 12:30:11 -0700161 if exit_status and not exception and not ignore_errors:
Stephen Warren10e50632016-01-15 11:15:24 -0700162 exception = Exception('Exit code: ' + str(exit_status))
163 if exception:
164 output += str(exception) + '\n'
165 self.logfile.write(self, output)
166 if self.chained_file:
167 self.chained_file.write(output)
Stephen Warrenb1c556a2017-10-27 11:04:08 -0600168 self.logfile.timestamp()
Simon Glass4134caf2016-07-03 09:40:38 -0600169
170 # Store the output so it can be accessed if we raise an exception.
171 self.output = output
Simon Glass95adb802016-07-31 17:35:03 -0600172 self.exit_status = exit_status
Stephen Warren10e50632016-01-15 11:15:24 -0700173 if exception:
174 raise exception
Simon Glass9f3c9e92016-07-03 09:40:37 -0600175 return output
Stephen Warren10e50632016-01-15 11:15:24 -0700176
177class SectionCtxMgr(object):
Stephen Warren75e731e2016-01-26 13:41:30 -0700178 """A context manager for Python's "with" statement, which allows a certain
Stephen Warren10e50632016-01-15 11:15:24 -0700179 portion of test code to be logged to a separate section of the log file.
180 Objects of this type should be created by factory functions in the Logfile
Stephen Warren75e731e2016-01-26 13:41:30 -0700181 class rather than directly."""
Stephen Warren10e50632016-01-15 11:15:24 -0700182
Stephen Warrene3f2a502016-02-03 16:46:34 -0700183 def __init__(self, log, marker, anchor):
Stephen Warren75e731e2016-01-26 13:41:30 -0700184 """Initialize a new object.
Stephen Warren10e50632016-01-15 11:15:24 -0700185
186 Args:
187 log: The Logfile object to log to.
188 marker: The name of the nested log section.
Stephen Warrene3f2a502016-02-03 16:46:34 -0700189 anchor: The anchor value to pass to start_section().
Stephen Warren10e50632016-01-15 11:15:24 -0700190
191 Returns:
192 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700193 """
Stephen Warren10e50632016-01-15 11:15:24 -0700194
195 self.log = log
196 self.marker = marker
Stephen Warrene3f2a502016-02-03 16:46:34 -0700197 self.anchor = anchor
Stephen Warren10e50632016-01-15 11:15:24 -0700198
199 def __enter__(self):
Stephen Warrene3f2a502016-02-03 16:46:34 -0700200 self.anchor = self.log.start_section(self.marker, self.anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700201
202 def __exit__(self, extype, value, traceback):
203 self.log.end_section(self.marker)
204
205class Logfile(object):
Stephen Warren75e731e2016-01-26 13:41:30 -0700206 """Generates an HTML-formatted log file containing multiple streams of
207 data, each represented in a well-delineated/-structured fashion."""
Stephen Warren10e50632016-01-15 11:15:24 -0700208
209 def __init__(self, fn):
Stephen Warren75e731e2016-01-26 13:41:30 -0700210 """Initialize a new object.
Stephen Warren10e50632016-01-15 11:15:24 -0700211
212 Args:
213 fn: The filename to write to.
214
215 Returns:
216 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700217 """
Stephen Warren10e50632016-01-15 11:15:24 -0700218
Stephen Warren3deb8962016-01-26 13:41:31 -0700219 self.f = open(fn, 'wt')
Stephen Warren10e50632016-01-15 11:15:24 -0700220 self.last_stream = None
221 self.blocks = []
222 self.cur_evt = 1
Stephen Warrene3f2a502016-02-03 16:46:34 -0700223 self.anchor = 0
Stephen Warrenb1c556a2017-10-27 11:04:08 -0600224 self.timestamp_start = self._get_time()
225 self.timestamp_prev = self.timestamp_start
226 self.timestamp_blocks = []
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700227 self.seen_warning = False
Stephen Warrene3f2a502016-02-03 16:46:34 -0700228
Stephen Warren3deb8962016-01-26 13:41:31 -0700229 shutil.copy(mod_dir + '/multiplexed_log.css', os.path.dirname(fn))
230 self.f.write('''\
Stephen Warren10e50632016-01-15 11:15:24 -0700231<html>
232<head>
233<link rel="stylesheet" type="text/css" href="multiplexed_log.css">
Stephen Warrene3f2a502016-02-03 16:46:34 -0700234<script src="http://code.jquery.com/jquery.min.js"></script>
235<script>
236$(document).ready(function () {
237 // Copy status report HTML to start of log for easy access
238 sts = $(".block#status_report")[0].outerHTML;
239 $("tt").prepend(sts);
240
241 // Add expand/contract buttons to all block headers
242 btns = "<span class=\\\"block-expand hidden\\\">[+] </span>" +
243 "<span class=\\\"block-contract\\\">[-] </span>";
244 $(".block-header").prepend(btns);
245
246 // Pre-contract all blocks which passed, leaving only problem cases
247 // expanded, to highlight issues the user should look at.
248 // Only top-level blocks (sections) should have any status
249 passed_bcs = $(".block-content:has(.status-pass)");
250 // Some blocks might have multiple status entries (e.g. the status
251 // report), so take care not to hide blocks with partial success.
252 passed_bcs = passed_bcs.not(":has(.status-fail)");
253 passed_bcs = passed_bcs.not(":has(.status-xfail)");
254 passed_bcs = passed_bcs.not(":has(.status-xpass)");
255 passed_bcs = passed_bcs.not(":has(.status-skipped)");
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700256 passed_bcs = passed_bcs.not(":has(.status-warning)");
Stephen Warrene3f2a502016-02-03 16:46:34 -0700257 // Hide the passed blocks
258 passed_bcs.addClass("hidden");
259 // Flip the expand/contract button hiding for those blocks.
260 bhs = passed_bcs.parent().children(".block-header")
261 bhs.children(".block-expand").removeClass("hidden");
262 bhs.children(".block-contract").addClass("hidden");
263
264 // Add click handler to block headers.
265 // The handler expands/contracts the block.
266 $(".block-header").on("click", function (e) {
267 var header = $(this);
268 var content = header.next(".block-content");
269 var expanded = !content.hasClass("hidden");
270 if (expanded) {
271 content.addClass("hidden");
272 header.children(".block-expand").first().removeClass("hidden");
273 header.children(".block-contract").first().addClass("hidden");
274 } else {
275 header.children(".block-contract").first().removeClass("hidden");
276 header.children(".block-expand").first().addClass("hidden");
277 content.removeClass("hidden");
278 }
279 });
280
281 // When clicking on a link, expand the target block
282 $("a").on("click", function (e) {
283 var block = $($(this).attr("href"));
284 var header = block.children(".block-header");
285 var content = block.children(".block-content").first();
286 header.children(".block-contract").first().removeClass("hidden");
287 header.children(".block-expand").first().addClass("hidden");
288 content.removeClass("hidden");
289 });
290});
291</script>
Stephen Warren10e50632016-01-15 11:15:24 -0700292</head>
293<body>
294<tt>
Stephen Warren3deb8962016-01-26 13:41:31 -0700295''')
Stephen Warren10e50632016-01-15 11:15:24 -0700296
297 def close(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700298 """Close the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700299
300 After calling this function, no more data may be written to the log.
301
302 Args:
303 None.
304
305 Returns:
306 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700307 """
Stephen Warren10e50632016-01-15 11:15:24 -0700308
Stephen Warren3deb8962016-01-26 13:41:31 -0700309 self.f.write('''\
Stephen Warren10e50632016-01-15 11:15:24 -0700310</tt>
311</body>
312</html>
Stephen Warren3deb8962016-01-26 13:41:31 -0700313''')
Stephen Warren10e50632016-01-15 11:15:24 -0700314 self.f.close()
315
316 # The set of characters that should be represented as hexadecimal codes in
317 # the log file.
Stephen Warren3deb8962016-01-26 13:41:31 -0700318 _nonprint = ('%' + ''.join(chr(c) for c in range(0, 32) if c not in (9, 10)) +
319 ''.join(chr(c) for c in range(127, 256)))
Stephen Warren10e50632016-01-15 11:15:24 -0700320
321 def _escape(self, data):
Stephen Warren75e731e2016-01-26 13:41:30 -0700322 """Render data format suitable for inclusion in an HTML document.
Stephen Warren10e50632016-01-15 11:15:24 -0700323
324 This includes HTML-escaping certain characters, and translating
325 control characters to a hexadecimal representation.
326
327 Args:
328 data: The raw string data to be escaped.
329
330 Returns:
331 An escaped version of the data.
Stephen Warren75e731e2016-01-26 13:41:30 -0700332 """
Stephen Warren10e50632016-01-15 11:15:24 -0700333
Stephen Warren3deb8962016-01-26 13:41:31 -0700334 data = data.replace(chr(13), '')
335 data = ''.join((c in self._nonprint) and ('%%%02x' % ord(c)) or
Stephen Warren10e50632016-01-15 11:15:24 -0700336 c for c in data)
337 data = cgi.escape(data)
338 return data
339
340 def _terminate_stream(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700341 """Write HTML to the log file to terminate the current stream's data.
Stephen Warren10e50632016-01-15 11:15:24 -0700342
343 Args:
344 None.
345
346 Returns:
347 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700348 """
Stephen Warren10e50632016-01-15 11:15:24 -0700349
350 self.cur_evt += 1
351 if not self.last_stream:
352 return
Stephen Warren3deb8962016-01-26 13:41:31 -0700353 self.f.write('</pre>\n')
Stephen Warrene3f2a502016-02-03 16:46:34 -0700354 self.f.write('<div class="stream-trailer block-trailer">End stream: ' +
Stephen Warren3deb8962016-01-26 13:41:31 -0700355 self.last_stream.name + '</div>\n')
356 self.f.write('</div>\n')
Stephen Warrene3f2a502016-02-03 16:46:34 -0700357 self.f.write('</div>\n')
Stephen Warren10e50632016-01-15 11:15:24 -0700358 self.last_stream = None
359
Stephen Warrene3f2a502016-02-03 16:46:34 -0700360 def _note(self, note_type, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700361 """Write a note or one-off message to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700362
363 Args:
364 note_type: The type of note. This must be a value supported by the
365 accompanying multiplexed_log.css.
366 msg: The note/message to log.
Stephen Warrene3f2a502016-02-03 16:46:34 -0700367 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700368
369 Returns:
370 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700371 """
Stephen Warren10e50632016-01-15 11:15:24 -0700372
373 self._terminate_stream()
Stephen Warrene3f2a502016-02-03 16:46:34 -0700374 self.f.write('<div class="' + note_type + '">\n')
Stephen Warrene3f2a502016-02-03 16:46:34 -0700375 self.f.write('<pre>')
Stephen Warrene0d2aee2017-09-18 11:50:47 -0600376 if anchor:
377 self.f.write('<a href="#%s">' % anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700378 self.f.write(self._escape(msg))
Stephen Warrene3f2a502016-02-03 16:46:34 -0700379 if anchor:
Stephen Warrene0d2aee2017-09-18 11:50:47 -0600380 self.f.write('</a>')
381 self.f.write('\n</pre>\n')
Stephen Warrene3f2a502016-02-03 16:46:34 -0700382 self.f.write('</div>\n')
Stephen Warren10e50632016-01-15 11:15:24 -0700383
Stephen Warrene3f2a502016-02-03 16:46:34 -0700384 def start_section(self, marker, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700385 """Begin a new nested section in the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700386
387 Args:
388 marker: The name of the section that is starting.
Stephen Warrene3f2a502016-02-03 16:46:34 -0700389 anchor: The value to use for the anchor. If None, a unique value
390 will be calculated and used
Stephen Warren10e50632016-01-15 11:15:24 -0700391
392 Returns:
Stephen Warrene3f2a502016-02-03 16:46:34 -0700393 Name of the HTML anchor emitted before section.
Stephen Warren75e731e2016-01-26 13:41:30 -0700394 """
Stephen Warren10e50632016-01-15 11:15:24 -0700395
396 self._terminate_stream()
397 self.blocks.append(marker)
Stephen Warrenb1c556a2017-10-27 11:04:08 -0600398 self.timestamp_blocks.append(self._get_time())
Stephen Warrene3f2a502016-02-03 16:46:34 -0700399 if not anchor:
400 self.anchor += 1
401 anchor = str(self.anchor)
Stephen Warren3deb8962016-01-26 13:41:31 -0700402 blk_path = '/'.join(self.blocks)
Stephen Warrene3f2a502016-02-03 16:46:34 -0700403 self.f.write('<div class="section block" id="' + anchor + '">\n')
404 self.f.write('<div class="section-header block-header">Section: ' +
405 blk_path + '</div>\n')
406 self.f.write('<div class="section-content block-content">\n')
Stephen Warrenb1c556a2017-10-27 11:04:08 -0600407 self.timestamp()
Stephen Warrene3f2a502016-02-03 16:46:34 -0700408
409 return anchor
Stephen Warren10e50632016-01-15 11:15:24 -0700410
411 def end_section(self, marker):
Stephen Warren75e731e2016-01-26 13:41:30 -0700412 """Terminate the current nested section in the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700413
414 This function validates proper nesting of start_section() and
415 end_section() calls. If a mismatch is found, an exception is raised.
416
417 Args:
418 marker: The name of the section that is ending.
419
420 Returns:
421 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700422 """
Stephen Warren10e50632016-01-15 11:15:24 -0700423
424 if (not self.blocks) or (marker != self.blocks[-1]):
Stephen Warren3deb8962016-01-26 13:41:31 -0700425 raise Exception('Block nesting mismatch: "%s" "%s"' %
426 (marker, '/'.join(self.blocks)))
Stephen Warren10e50632016-01-15 11:15:24 -0700427 self._terminate_stream()
Stephen Warrenb1c556a2017-10-27 11:04:08 -0600428 timestamp_now = self._get_time()
429 timestamp_section_start = self.timestamp_blocks.pop()
430 delta_section = timestamp_now - timestamp_section_start
431 self._note("timestamp",
432 "TIME: SINCE-SECTION: " + str(delta_section))
Stephen Warren3deb8962016-01-26 13:41:31 -0700433 blk_path = '/'.join(self.blocks)
Stephen Warrene3f2a502016-02-03 16:46:34 -0700434 self.f.write('<div class="section-trailer block-trailer">' +
435 'End section: ' + blk_path + '</div>\n')
436 self.f.write('</div>\n')
Stephen Warren3deb8962016-01-26 13:41:31 -0700437 self.f.write('</div>\n')
Stephen Warren10e50632016-01-15 11:15:24 -0700438 self.blocks.pop()
439
Stephen Warrene3f2a502016-02-03 16:46:34 -0700440 def section(self, marker, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700441 """Create a temporary section in the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700442
443 This function creates a context manager for Python's "with" statement,
444 which allows a certain portion of test code to be logged to a separate
445 section of the log file.
446
447 Usage:
448 with log.section("somename"):
449 some test code
450
451 Args:
452 marker: The name of the nested section.
Stephen Warrene3f2a502016-02-03 16:46:34 -0700453 anchor: The anchor value to pass to start_section().
Stephen Warren10e50632016-01-15 11:15:24 -0700454
455 Returns:
456 A context manager object.
Stephen Warren75e731e2016-01-26 13:41:30 -0700457 """
Stephen Warren10e50632016-01-15 11:15:24 -0700458
Stephen Warrene3f2a502016-02-03 16:46:34 -0700459 return SectionCtxMgr(self, marker, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700460
461 def error(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700462 """Write an error note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700463
464 Args:
465 msg: A message describing the error.
466
467 Returns:
468 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700469 """
Stephen Warren10e50632016-01-15 11:15:24 -0700470
471 self._note("error", msg)
472
473 def warning(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700474 """Write an warning note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700475
476 Args:
477 msg: A message describing the warning.
478
479 Returns:
480 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700481 """
Stephen Warren10e50632016-01-15 11:15:24 -0700482
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700483 self.seen_warning = True
Stephen Warren10e50632016-01-15 11:15:24 -0700484 self._note("warning", msg)
485
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700486 def get_and_reset_warning(self):
487 """Get and reset the log warning flag.
488
489 Args:
490 None
491
492 Returns:
493 Whether a warning was seen since the last call.
494 """
495
496 ret = self.seen_warning
497 self.seen_warning = False
498 return ret
499
Stephen Warren10e50632016-01-15 11:15:24 -0700500 def info(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700501 """Write an informational note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700502
503 Args:
504 msg: An informational message.
505
506 Returns:
507 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700508 """
Stephen Warren10e50632016-01-15 11:15:24 -0700509
510 self._note("info", msg)
511
512 def action(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700513 """Write an action note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700514
515 Args:
516 msg: A message describing the action that is being logged.
517
518 Returns:
519 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700520 """
Stephen Warren10e50632016-01-15 11:15:24 -0700521
522 self._note("action", msg)
523
Stephen Warrenb1c556a2017-10-27 11:04:08 -0600524 def _get_time(self):
525 return datetime.datetime.now()
526
527 def timestamp(self):
528 """Write a timestamp to the log file.
529
530 Args:
531 None
532
533 Returns:
534 Nothing.
535 """
536
537 timestamp_now = self._get_time()
538 delta_prev = timestamp_now - self.timestamp_prev
539 delta_start = timestamp_now - self.timestamp_start
540 self.timestamp_prev = timestamp_now
541
542 self._note("timestamp",
543 "TIME: NOW: " + timestamp_now.strftime("%Y/%m/%d %H:%M:%S.%f"))
544 self._note("timestamp",
545 "TIME: SINCE-PREV: " + str(delta_prev))
546 self._note("timestamp",
547 "TIME: SINCE-START: " + str(delta_start))
548
Stephen Warrene3f2a502016-02-03 16:46:34 -0700549 def status_pass(self, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700550 """Write a note to the log file describing test(s) which passed.
Stephen Warren10e50632016-01-15 11:15:24 -0700551
552 Args:
Stephen Warren25b05242016-01-27 23:57:51 -0700553 msg: A message describing the passed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700554 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700555
556 Returns:
557 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700558 """
Stephen Warren10e50632016-01-15 11:15:24 -0700559
Stephen Warrene3f2a502016-02-03 16:46:34 -0700560 self._note("status-pass", msg, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700561
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700562 def status_warning(self, msg, anchor=None):
563 """Write a note to the log file describing test(s) which passed.
564
565 Args:
566 msg: A message describing the passed test(s).
567 anchor: Optional internal link target.
568
569 Returns:
570 Nothing.
571 """
572
573 self._note("status-warning", msg, anchor)
574
Stephen Warrene3f2a502016-02-03 16:46:34 -0700575 def status_skipped(self, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700576 """Write a note to the log file describing skipped test(s).
Stephen Warren10e50632016-01-15 11:15:24 -0700577
578 Args:
Stephen Warren25b05242016-01-27 23:57:51 -0700579 msg: A message describing the skipped test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700580 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700581
582 Returns:
583 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700584 """
Stephen Warren10e50632016-01-15 11:15:24 -0700585
Stephen Warrene3f2a502016-02-03 16:46:34 -0700586 self._note("status-skipped", msg, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700587
Stephen Warrene3f2a502016-02-03 16:46:34 -0700588 def status_xfail(self, msg, anchor=None):
Stephen Warren25b05242016-01-27 23:57:51 -0700589 """Write a note to the log file describing xfailed test(s).
590
591 Args:
592 msg: A message describing the xfailed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700593 anchor: Optional internal link target.
Stephen Warren25b05242016-01-27 23:57:51 -0700594
595 Returns:
596 Nothing.
597 """
598
Stephen Warrene3f2a502016-02-03 16:46:34 -0700599 self._note("status-xfail", msg, anchor)
Stephen Warren25b05242016-01-27 23:57:51 -0700600
Stephen Warrene3f2a502016-02-03 16:46:34 -0700601 def status_xpass(self, msg, anchor=None):
Stephen Warren25b05242016-01-27 23:57:51 -0700602 """Write a note to the log file describing xpassed test(s).
603
604 Args:
605 msg: A message describing the xpassed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700606 anchor: Optional internal link target.
Stephen Warren25b05242016-01-27 23:57:51 -0700607
608 Returns:
609 Nothing.
610 """
611
Stephen Warrene3f2a502016-02-03 16:46:34 -0700612 self._note("status-xpass", msg, anchor)
Stephen Warren25b05242016-01-27 23:57:51 -0700613
Stephen Warrene3f2a502016-02-03 16:46:34 -0700614 def status_fail(self, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700615 """Write a note to the log file describing failed test(s).
Stephen Warren10e50632016-01-15 11:15:24 -0700616
617 Args:
Stephen Warren25b05242016-01-27 23:57:51 -0700618 msg: A message describing the failed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700619 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700620
621 Returns:
622 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700623 """
Stephen Warren10e50632016-01-15 11:15:24 -0700624
Stephen Warrene3f2a502016-02-03 16:46:34 -0700625 self._note("status-fail", msg, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700626
627 def get_stream(self, name, chained_file=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700628 """Create an object to log a single stream's data into the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700629
630 This creates a "file-like" object that can be written to in order to
631 write a single stream's data to the log file. The implementation will
632 handle any required interleaving of data (from multiple streams) in
633 the log, in a way that makes it obvious which stream each bit of data
634 came from.
635
636 Args:
637 name: The name of the stream.
638 chained_file: The file-like object to which all stream data should
639 be logged to in addition to this log. Can be None.
640
641 Returns:
642 A file-like object.
Stephen Warren75e731e2016-01-26 13:41:30 -0700643 """
Stephen Warren10e50632016-01-15 11:15:24 -0700644
645 return LogfileStream(self, name, chained_file)
646
647 def get_runner(self, name, chained_file=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700648 """Create an object that executes processes and logs their output.
Stephen Warren10e50632016-01-15 11:15:24 -0700649
650 Args:
651 name: The name of this sub-process.
652 chained_file: The file-like object to which all stream data should
653 be logged to in addition to logfile. Can be None.
654
655 Returns:
656 A RunAndLog object.
Stephen Warren75e731e2016-01-26 13:41:30 -0700657 """
Stephen Warren10e50632016-01-15 11:15:24 -0700658
659 return RunAndLog(self, name, chained_file)
660
661 def write(self, stream, data, implicit=False):
Stephen Warren75e731e2016-01-26 13:41:30 -0700662 """Write stream data into the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700663
664 This function should only be used by instances of LogfileStream or
665 RunAndLog.
666
667 Args:
668 stream: The stream whose data is being logged.
669 data: The data to log.
670 implicit: Boolean indicating whether data actually appeared in the
671 stream, or was implicitly generated. A valid use-case is to
672 repeat a shell prompt at the start of each separate log
673 section, which makes the log sections more readable in
674 isolation.
675
676 Returns:
677 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700678 """
Stephen Warren10e50632016-01-15 11:15:24 -0700679
680 if stream != self.last_stream:
681 self._terminate_stream()
Stephen Warrene3f2a502016-02-03 16:46:34 -0700682 self.f.write('<div class="stream block">\n')
683 self.f.write('<div class="stream-header block-header">Stream: ' +
684 stream.name + '</div>\n')
685 self.f.write('<div class="stream-content block-content">\n')
Stephen Warren3deb8962016-01-26 13:41:31 -0700686 self.f.write('<pre>')
Stephen Warren10e50632016-01-15 11:15:24 -0700687 if implicit:
Stephen Warren3deb8962016-01-26 13:41:31 -0700688 self.f.write('<span class="implicit">')
Stephen Warren10e50632016-01-15 11:15:24 -0700689 self.f.write(self._escape(data))
690 if implicit:
Stephen Warren3deb8962016-01-26 13:41:31 -0700691 self.f.write('</span>')
Stephen Warren10e50632016-01-15 11:15:24 -0700692 self.last_stream = stream
693
694 def flush(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700695 """Flush the log stream, to ensure correct log interleaving.
Stephen Warren10e50632016-01-15 11:15:24 -0700696
697 Args:
698 None.
699
700 Returns:
701 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700702 """
Stephen Warren10e50632016-01-15 11:15:24 -0700703
704 self.f.flush()