blob: f23d5dec68cd3b8c17e83c09b4820d8d4900fb0f [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0
Stephen Warren10e50632016-01-15 11:15:24 -07002# Copyright (c) 2015 Stephen Warren
3# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
Stephen Warren10e50632016-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
8import cgi
Stephen Warrenb1c556a2017-10-27 11:04:08 -06009import datetime
Stephen Warren10e50632016-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 Warren75e731e2016-01-26 13:41:30 -070017 """A file-like object used to write a single logical stream of data into
Stephen Warren10e50632016-01-15 11:15:24 -070018 a multiplexed log file. Objects of this type should be created by factory
Stephen Warren75e731e2016-01-26 13:41:30 -070019 functions in the Logfile class rather than directly."""
Stephen Warren10e50632016-01-15 11:15:24 -070020
21 def __init__(self, logfile, name, chained_file):
Stephen Warren75e731e2016-01-26 13:41:30 -070022 """Initialize a new object.
Stephen Warren10e50632016-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 Warren75e731e2016-01-26 13:41:30 -070032 """
Stephen Warren10e50632016-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 Warren75e731e2016-01-26 13:41:30 -070039 """Dummy function so that this class is "file-like".
Stephen Warren10e50632016-01-15 11:15:24 -070040
41 Args:
42 None.
43
44 Returns:
45 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -070046 """
Stephen Warren10e50632016-01-15 11:15:24 -070047
48 pass
49
50 def write(self, data, implicit=False):
Stephen Warren75e731e2016-01-26 13:41:30 -070051 """Write data to the log stream.
Stephen Warren10e50632016-01-15 11:15:24 -070052
53 Args:
54 data: The data to write tot he file.
55 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 Warren75e731e2016-01-26 13:41:30 -070063 """
Stephen Warren10e50632016-01-15 11:15:24 -070064
65 self.logfile.write(self, data, implicit)
66 if self.chained_file:
67 self.chained_file.write(data)
68
69 def flush(self):
Stephen Warren75e731e2016-01-26 13:41:30 -070070 """Flush the log stream, to ensure correct log interleaving.
Stephen Warren10e50632016-01-15 11:15:24 -070071
72 Args:
73 None.
74
75 Returns:
76 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -070077 """
Stephen Warren10e50632016-01-15 11:15:24 -070078
79 self.logfile.flush()
80 if self.chained_file:
81 self.chained_file.flush()
82
83class RunAndLog(object):
Stephen Warren75e731e2016-01-26 13:41:30 -070084 """A utility object used to execute sub-processes and log their output to
Stephen Warren10e50632016-01-15 11:15:24 -070085 a multiplexed log file. Objects of this type should be created by factory
Stephen Warren75e731e2016-01-26 13:41:30 -070086 functions in the Logfile class rather than directly."""
Stephen Warren10e50632016-01-15 11:15:24 -070087
88 def __init__(self, logfile, name, chained_file):
Stephen Warren75e731e2016-01-26 13:41:30 -070089 """Initialize a new object.
Stephen Warren10e50632016-01-15 11:15:24 -070090
91 Args:
92 logfile: The Logfile object to log to.
93 name: The name of this log stream or sub-process.
94 chained_file: The file-like object to which all stream data should
95 be logged to in addition to logfile. Can be None.
96
97 Returns:
98 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -070099 """
Stephen Warren10e50632016-01-15 11:15:24 -0700100
101 self.logfile = logfile
102 self.name = name
103 self.chained_file = chained_file
Simon Glass4134caf2016-07-03 09:40:38 -0600104 self.output = None
Simon Glass95adb802016-07-31 17:35:03 -0600105 self.exit_status = None
Stephen Warren10e50632016-01-15 11:15:24 -0700106
107 def close(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700108 """Clean up any resources managed by this object."""
Stephen Warren10e50632016-01-15 11:15:24 -0700109 pass
110
Stephen Warren118e37e2016-01-22 12:30:11 -0700111 def run(self, cmd, cwd=None, ignore_errors=False):
Stephen Warren75e731e2016-01-26 13:41:30 -0700112 """Run a command as a sub-process, and log the results.
Stephen Warren10e50632016-01-15 11:15:24 -0700113
Simon Glass4134caf2016-07-03 09:40:38 -0600114 The output is available at self.output which can be useful if there is
115 an exception.
116
Stephen Warren10e50632016-01-15 11:15:24 -0700117 Args:
118 cmd: The command to execute.
119 cwd: The directory to run the command in. Can be None to use the
120 current directory.
Stephen Warren118e37e2016-01-22 12:30:11 -0700121 ignore_errors: Indicate whether to ignore errors. If True, the
122 function will simply return if the command cannot be executed
123 or exits with an error code, otherwise an exception will be
124 raised if such problems occur.
Stephen Warren10e50632016-01-15 11:15:24 -0700125
126 Returns:
Simon Glass9f3c9e92016-07-03 09:40:37 -0600127 The output as a string.
Stephen Warren75e731e2016-01-26 13:41:30 -0700128 """
Stephen Warren10e50632016-01-15 11:15:24 -0700129
Stephen Warren3deb8962016-01-26 13:41:31 -0700130 msg = '+' + ' '.join(cmd) + '\n'
Stephen Warren10e50632016-01-15 11:15:24 -0700131 if self.chained_file:
132 self.chained_file.write(msg)
133 self.logfile.write(self, msg)
134
135 try:
136 p = subprocess.Popen(cmd, cwd=cwd,
137 stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
138 (stdout, stderr) = p.communicate()
139 output = ''
140 if stdout:
141 if stderr:
142 output += 'stdout:\n'
143 output += stdout
144 if stderr:
145 if stdout:
146 output += 'stderr:\n'
147 output += stderr
148 exit_status = p.returncode
149 exception = None
150 except subprocess.CalledProcessError as cpe:
151 output = cpe.output
152 exit_status = cpe.returncode
153 exception = cpe
154 except Exception as e:
155 output = ''
156 exit_status = 0
157 exception = e
158 if output and not output.endswith('\n'):
159 output += '\n'
Stephen Warren118e37e2016-01-22 12:30:11 -0700160 if exit_status and not exception and not ignore_errors:
Stephen Warren10e50632016-01-15 11:15:24 -0700161 exception = Exception('Exit code: ' + str(exit_status))
162 if exception:
163 output += str(exception) + '\n'
164 self.logfile.write(self, output)
165 if self.chained_file:
166 self.chained_file.write(output)
Stephen Warrenb1c556a2017-10-27 11:04:08 -0600167 self.logfile.timestamp()
Simon Glass4134caf2016-07-03 09:40:38 -0600168
169 # Store the output so it can be accessed if we raise an exception.
170 self.output = output
Simon Glass95adb802016-07-31 17:35:03 -0600171 self.exit_status = exit_status
Stephen Warren10e50632016-01-15 11:15:24 -0700172 if exception:
173 raise exception
Simon Glass9f3c9e92016-07-03 09:40:37 -0600174 return output
Stephen Warren10e50632016-01-15 11:15:24 -0700175
176class SectionCtxMgr(object):
Stephen Warren75e731e2016-01-26 13:41:30 -0700177 """A context manager for Python's "with" statement, which allows a certain
Stephen Warren10e50632016-01-15 11:15:24 -0700178 portion of test code to be logged to a separate section of the log file.
179 Objects of this type should be created by factory functions in the Logfile
Stephen Warren75e731e2016-01-26 13:41:30 -0700180 class rather than directly."""
Stephen Warren10e50632016-01-15 11:15:24 -0700181
Stephen Warrene3f2a502016-02-03 16:46:34 -0700182 def __init__(self, log, marker, anchor):
Stephen Warren75e731e2016-01-26 13:41:30 -0700183 """Initialize a new object.
Stephen Warren10e50632016-01-15 11:15:24 -0700184
185 Args:
186 log: The Logfile object to log to.
187 marker: The name of the nested log section.
Stephen Warrene3f2a502016-02-03 16:46:34 -0700188 anchor: The anchor value to pass to start_section().
Stephen Warren10e50632016-01-15 11:15:24 -0700189
190 Returns:
191 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700192 """
Stephen Warren10e50632016-01-15 11:15:24 -0700193
194 self.log = log
195 self.marker = marker
Stephen Warrene3f2a502016-02-03 16:46:34 -0700196 self.anchor = anchor
Stephen Warren10e50632016-01-15 11:15:24 -0700197
198 def __enter__(self):
Stephen Warrene3f2a502016-02-03 16:46:34 -0700199 self.anchor = self.log.start_section(self.marker, self.anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700200
201 def __exit__(self, extype, value, traceback):
202 self.log.end_section(self.marker)
203
204class Logfile(object):
Stephen Warren75e731e2016-01-26 13:41:30 -0700205 """Generates an HTML-formatted log file containing multiple streams of
206 data, each represented in a well-delineated/-structured fashion."""
Stephen Warren10e50632016-01-15 11:15:24 -0700207
208 def __init__(self, fn):
Stephen Warren75e731e2016-01-26 13:41:30 -0700209 """Initialize a new object.
Stephen Warren10e50632016-01-15 11:15:24 -0700210
211 Args:
212 fn: The filename to write to.
213
214 Returns:
215 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700216 """
Stephen Warren10e50632016-01-15 11:15:24 -0700217
Stephen Warren3deb8962016-01-26 13:41:31 -0700218 self.f = open(fn, 'wt')
Stephen Warren10e50632016-01-15 11:15:24 -0700219 self.last_stream = None
220 self.blocks = []
221 self.cur_evt = 1
Stephen Warrene3f2a502016-02-03 16:46:34 -0700222 self.anchor = 0
Stephen Warrenb1c556a2017-10-27 11:04:08 -0600223 self.timestamp_start = self._get_time()
224 self.timestamp_prev = self.timestamp_start
225 self.timestamp_blocks = []
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700226 self.seen_warning = False
Stephen Warrene3f2a502016-02-03 16:46:34 -0700227
Stephen Warren3deb8962016-01-26 13:41:31 -0700228 shutil.copy(mod_dir + '/multiplexed_log.css', os.path.dirname(fn))
229 self.f.write('''\
Stephen Warren10e50632016-01-15 11:15:24 -0700230<html>
231<head>
232<link rel="stylesheet" type="text/css" href="multiplexed_log.css">
Stephen Warrene3f2a502016-02-03 16:46:34 -0700233<script src="http://code.jquery.com/jquery.min.js"></script>
234<script>
235$(document).ready(function () {
236 // Copy status report HTML to start of log for easy access
237 sts = $(".block#status_report")[0].outerHTML;
238 $("tt").prepend(sts);
239
240 // Add expand/contract buttons to all block headers
241 btns = "<span class=\\\"block-expand hidden\\\">[+] </span>" +
242 "<span class=\\\"block-contract\\\">[-] </span>";
243 $(".block-header").prepend(btns);
244
245 // Pre-contract all blocks which passed, leaving only problem cases
246 // expanded, to highlight issues the user should look at.
247 // Only top-level blocks (sections) should have any status
248 passed_bcs = $(".block-content:has(.status-pass)");
249 // Some blocks might have multiple status entries (e.g. the status
250 // report), so take care not to hide blocks with partial success.
251 passed_bcs = passed_bcs.not(":has(.status-fail)");
252 passed_bcs = passed_bcs.not(":has(.status-xfail)");
253 passed_bcs = passed_bcs.not(":has(.status-xpass)");
254 passed_bcs = passed_bcs.not(":has(.status-skipped)");
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700255 passed_bcs = passed_bcs.not(":has(.status-warning)");
Stephen Warrene3f2a502016-02-03 16:46:34 -0700256 // Hide the passed blocks
257 passed_bcs.addClass("hidden");
258 // Flip the expand/contract button hiding for those blocks.
259 bhs = passed_bcs.parent().children(".block-header")
260 bhs.children(".block-expand").removeClass("hidden");
261 bhs.children(".block-contract").addClass("hidden");
262
263 // Add click handler to block headers.
264 // The handler expands/contracts the block.
265 $(".block-header").on("click", function (e) {
266 var header = $(this);
267 var content = header.next(".block-content");
268 var expanded = !content.hasClass("hidden");
269 if (expanded) {
270 content.addClass("hidden");
271 header.children(".block-expand").first().removeClass("hidden");
272 header.children(".block-contract").first().addClass("hidden");
273 } else {
274 header.children(".block-contract").first().removeClass("hidden");
275 header.children(".block-expand").first().addClass("hidden");
276 content.removeClass("hidden");
277 }
278 });
279
280 // When clicking on a link, expand the target block
281 $("a").on("click", function (e) {
282 var block = $($(this).attr("href"));
283 var header = block.children(".block-header");
284 var content = block.children(".block-content").first();
285 header.children(".block-contract").first().removeClass("hidden");
286 header.children(".block-expand").first().addClass("hidden");
287 content.removeClass("hidden");
288 });
289});
290</script>
Stephen Warren10e50632016-01-15 11:15:24 -0700291</head>
292<body>
293<tt>
Stephen Warren3deb8962016-01-26 13:41:31 -0700294''')
Stephen Warren10e50632016-01-15 11:15:24 -0700295
296 def close(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700297 """Close the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700298
299 After calling this function, no more data may be written to the log.
300
301 Args:
302 None.
303
304 Returns:
305 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700306 """
Stephen Warren10e50632016-01-15 11:15:24 -0700307
Stephen Warren3deb8962016-01-26 13:41:31 -0700308 self.f.write('''\
Stephen Warren10e50632016-01-15 11:15:24 -0700309</tt>
310</body>
311</html>
Stephen Warren3deb8962016-01-26 13:41:31 -0700312''')
Stephen Warren10e50632016-01-15 11:15:24 -0700313 self.f.close()
314
315 # The set of characters that should be represented as hexadecimal codes in
316 # the log file.
Stephen Warren3deb8962016-01-26 13:41:31 -0700317 _nonprint = ('%' + ''.join(chr(c) for c in range(0, 32) if c not in (9, 10)) +
318 ''.join(chr(c) for c in range(127, 256)))
Stephen Warren10e50632016-01-15 11:15:24 -0700319
320 def _escape(self, data):
Stephen Warren75e731e2016-01-26 13:41:30 -0700321 """Render data format suitable for inclusion in an HTML document.
Stephen Warren10e50632016-01-15 11:15:24 -0700322
323 This includes HTML-escaping certain characters, and translating
324 control characters to a hexadecimal representation.
325
326 Args:
327 data: The raw string data to be escaped.
328
329 Returns:
330 An escaped version of the data.
Stephen Warren75e731e2016-01-26 13:41:30 -0700331 """
Stephen Warren10e50632016-01-15 11:15:24 -0700332
Stephen Warren3deb8962016-01-26 13:41:31 -0700333 data = data.replace(chr(13), '')
334 data = ''.join((c in self._nonprint) and ('%%%02x' % ord(c)) or
Stephen Warren10e50632016-01-15 11:15:24 -0700335 c for c in data)
336 data = cgi.escape(data)
337 return data
338
339 def _terminate_stream(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700340 """Write HTML to the log file to terminate the current stream's data.
Stephen Warren10e50632016-01-15 11:15:24 -0700341
342 Args:
343 None.
344
345 Returns:
346 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700347 """
Stephen Warren10e50632016-01-15 11:15:24 -0700348
349 self.cur_evt += 1
350 if not self.last_stream:
351 return
Stephen Warren3deb8962016-01-26 13:41:31 -0700352 self.f.write('</pre>\n')
Stephen Warrene3f2a502016-02-03 16:46:34 -0700353 self.f.write('<div class="stream-trailer block-trailer">End stream: ' +
Stephen Warren3deb8962016-01-26 13:41:31 -0700354 self.last_stream.name + '</div>\n')
355 self.f.write('</div>\n')
Stephen Warrene3f2a502016-02-03 16:46:34 -0700356 self.f.write('</div>\n')
Stephen Warren10e50632016-01-15 11:15:24 -0700357 self.last_stream = None
358
Stephen Warrene3f2a502016-02-03 16:46:34 -0700359 def _note(self, note_type, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700360 """Write a note or one-off message to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700361
362 Args:
363 note_type: The type of note. This must be a value supported by the
364 accompanying multiplexed_log.css.
365 msg: The note/message to log.
Stephen Warrene3f2a502016-02-03 16:46:34 -0700366 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700367
368 Returns:
369 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700370 """
Stephen Warren10e50632016-01-15 11:15:24 -0700371
372 self._terminate_stream()
Stephen Warrene3f2a502016-02-03 16:46:34 -0700373 self.f.write('<div class="' + note_type + '">\n')
Stephen Warrene3f2a502016-02-03 16:46:34 -0700374 self.f.write('<pre>')
Stephen Warrene0d2aee2017-09-18 11:50:47 -0600375 if anchor:
376 self.f.write('<a href="#%s">' % anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700377 self.f.write(self._escape(msg))
Stephen Warrene3f2a502016-02-03 16:46:34 -0700378 if anchor:
Stephen Warrene0d2aee2017-09-18 11:50:47 -0600379 self.f.write('</a>')
380 self.f.write('\n</pre>\n')
Stephen Warrene3f2a502016-02-03 16:46:34 -0700381 self.f.write('</div>\n')
Stephen Warren10e50632016-01-15 11:15:24 -0700382
Stephen Warrene3f2a502016-02-03 16:46:34 -0700383 def start_section(self, marker, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700384 """Begin a new nested section in the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700385
386 Args:
387 marker: The name of the section that is starting.
Stephen Warrene3f2a502016-02-03 16:46:34 -0700388 anchor: The value to use for the anchor. If None, a unique value
389 will be calculated and used
Stephen Warren10e50632016-01-15 11:15:24 -0700390
391 Returns:
Stephen Warrene3f2a502016-02-03 16:46:34 -0700392 Name of the HTML anchor emitted before section.
Stephen Warren75e731e2016-01-26 13:41:30 -0700393 """
Stephen Warren10e50632016-01-15 11:15:24 -0700394
395 self._terminate_stream()
396 self.blocks.append(marker)
Stephen Warrenb1c556a2017-10-27 11:04:08 -0600397 self.timestamp_blocks.append(self._get_time())
Stephen Warrene3f2a502016-02-03 16:46:34 -0700398 if not anchor:
399 self.anchor += 1
400 anchor = str(self.anchor)
Stephen Warren3deb8962016-01-26 13:41:31 -0700401 blk_path = '/'.join(self.blocks)
Stephen Warrene3f2a502016-02-03 16:46:34 -0700402 self.f.write('<div class="section block" id="' + anchor + '">\n')
403 self.f.write('<div class="section-header block-header">Section: ' +
404 blk_path + '</div>\n')
405 self.f.write('<div class="section-content block-content">\n')
Stephen Warrenb1c556a2017-10-27 11:04:08 -0600406 self.timestamp()
Stephen Warrene3f2a502016-02-03 16:46:34 -0700407
408 return anchor
Stephen Warren10e50632016-01-15 11:15:24 -0700409
410 def end_section(self, marker):
Stephen Warren75e731e2016-01-26 13:41:30 -0700411 """Terminate the current nested section in the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700412
413 This function validates proper nesting of start_section() and
414 end_section() calls. If a mismatch is found, an exception is raised.
415
416 Args:
417 marker: The name of the section that is ending.
418
419 Returns:
420 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700421 """
Stephen Warren10e50632016-01-15 11:15:24 -0700422
423 if (not self.blocks) or (marker != self.blocks[-1]):
Stephen Warren3deb8962016-01-26 13:41:31 -0700424 raise Exception('Block nesting mismatch: "%s" "%s"' %
425 (marker, '/'.join(self.blocks)))
Stephen Warren10e50632016-01-15 11:15:24 -0700426 self._terminate_stream()
Stephen Warrenb1c556a2017-10-27 11:04:08 -0600427 timestamp_now = self._get_time()
428 timestamp_section_start = self.timestamp_blocks.pop()
429 delta_section = timestamp_now - timestamp_section_start
430 self._note("timestamp",
431 "TIME: SINCE-SECTION: " + str(delta_section))
Stephen Warren3deb8962016-01-26 13:41:31 -0700432 blk_path = '/'.join(self.blocks)
Stephen Warrene3f2a502016-02-03 16:46:34 -0700433 self.f.write('<div class="section-trailer block-trailer">' +
434 'End section: ' + blk_path + '</div>\n')
435 self.f.write('</div>\n')
Stephen Warren3deb8962016-01-26 13:41:31 -0700436 self.f.write('</div>\n')
Stephen Warren10e50632016-01-15 11:15:24 -0700437 self.blocks.pop()
438
Stephen Warrene3f2a502016-02-03 16:46:34 -0700439 def section(self, marker, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700440 """Create a temporary section in the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700441
442 This function creates a context manager for Python's "with" statement,
443 which allows a certain portion of test code to be logged to a separate
444 section of the log file.
445
446 Usage:
447 with log.section("somename"):
448 some test code
449
450 Args:
451 marker: The name of the nested section.
Stephen Warrene3f2a502016-02-03 16:46:34 -0700452 anchor: The anchor value to pass to start_section().
Stephen Warren10e50632016-01-15 11:15:24 -0700453
454 Returns:
455 A context manager object.
Stephen Warren75e731e2016-01-26 13:41:30 -0700456 """
Stephen Warren10e50632016-01-15 11:15:24 -0700457
Stephen Warrene3f2a502016-02-03 16:46:34 -0700458 return SectionCtxMgr(self, marker, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700459
460 def error(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700461 """Write an error note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700462
463 Args:
464 msg: A message describing the error.
465
466 Returns:
467 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700468 """
Stephen Warren10e50632016-01-15 11:15:24 -0700469
470 self._note("error", msg)
471
472 def warning(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700473 """Write an warning note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700474
475 Args:
476 msg: A message describing the warning.
477
478 Returns:
479 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700480 """
Stephen Warren10e50632016-01-15 11:15:24 -0700481
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700482 self.seen_warning = True
Stephen Warren10e50632016-01-15 11:15:24 -0700483 self._note("warning", msg)
484
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700485 def get_and_reset_warning(self):
486 """Get and reset the log warning flag.
487
488 Args:
489 None
490
491 Returns:
492 Whether a warning was seen since the last call.
493 """
494
495 ret = self.seen_warning
496 self.seen_warning = False
497 return ret
498
Stephen Warren10e50632016-01-15 11:15:24 -0700499 def info(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700500 """Write an informational note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700501
502 Args:
503 msg: An informational message.
504
505 Returns:
506 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700507 """
Stephen Warren10e50632016-01-15 11:15:24 -0700508
509 self._note("info", msg)
510
511 def action(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700512 """Write an action note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700513
514 Args:
515 msg: A message describing the action that is being logged.
516
517 Returns:
518 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700519 """
Stephen Warren10e50632016-01-15 11:15:24 -0700520
521 self._note("action", msg)
522
Stephen Warrenb1c556a2017-10-27 11:04:08 -0600523 def _get_time(self):
524 return datetime.datetime.now()
525
526 def timestamp(self):
527 """Write a timestamp to the log file.
528
529 Args:
530 None
531
532 Returns:
533 Nothing.
534 """
535
536 timestamp_now = self._get_time()
537 delta_prev = timestamp_now - self.timestamp_prev
538 delta_start = timestamp_now - self.timestamp_start
539 self.timestamp_prev = timestamp_now
540
541 self._note("timestamp",
542 "TIME: NOW: " + timestamp_now.strftime("%Y/%m/%d %H:%M:%S.%f"))
543 self._note("timestamp",
544 "TIME: SINCE-PREV: " + str(delta_prev))
545 self._note("timestamp",
546 "TIME: SINCE-START: " + str(delta_start))
547
Stephen Warrene3f2a502016-02-03 16:46:34 -0700548 def status_pass(self, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700549 """Write a note to the log file describing test(s) which passed.
Stephen Warren10e50632016-01-15 11:15:24 -0700550
551 Args:
Stephen Warren25b05242016-01-27 23:57:51 -0700552 msg: A message describing the passed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700553 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700554
555 Returns:
556 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700557 """
Stephen Warren10e50632016-01-15 11:15:24 -0700558
Stephen Warrene3f2a502016-02-03 16:46:34 -0700559 self._note("status-pass", msg, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700560
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700561 def status_warning(self, msg, anchor=None):
562 """Write a note to the log file describing test(s) which passed.
563
564 Args:
565 msg: A message describing the passed test(s).
566 anchor: Optional internal link target.
567
568 Returns:
569 Nothing.
570 """
571
572 self._note("status-warning", msg, anchor)
573
Stephen Warrene3f2a502016-02-03 16:46:34 -0700574 def status_skipped(self, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700575 """Write a note to the log file describing skipped test(s).
Stephen Warren10e50632016-01-15 11:15:24 -0700576
577 Args:
Stephen Warren25b05242016-01-27 23:57:51 -0700578 msg: A message describing the skipped test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700579 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700580
581 Returns:
582 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700583 """
Stephen Warren10e50632016-01-15 11:15:24 -0700584
Stephen Warrene3f2a502016-02-03 16:46:34 -0700585 self._note("status-skipped", msg, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700586
Stephen Warrene3f2a502016-02-03 16:46:34 -0700587 def status_xfail(self, msg, anchor=None):
Stephen Warren25b05242016-01-27 23:57:51 -0700588 """Write a note to the log file describing xfailed test(s).
589
590 Args:
591 msg: A message describing the xfailed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700592 anchor: Optional internal link target.
Stephen Warren25b05242016-01-27 23:57:51 -0700593
594 Returns:
595 Nothing.
596 """
597
Stephen Warrene3f2a502016-02-03 16:46:34 -0700598 self._note("status-xfail", msg, anchor)
Stephen Warren25b05242016-01-27 23:57:51 -0700599
Stephen Warrene3f2a502016-02-03 16:46:34 -0700600 def status_xpass(self, msg, anchor=None):
Stephen Warren25b05242016-01-27 23:57:51 -0700601 """Write a note to the log file describing xpassed test(s).
602
603 Args:
604 msg: A message describing the xpassed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700605 anchor: Optional internal link target.
Stephen Warren25b05242016-01-27 23:57:51 -0700606
607 Returns:
608 Nothing.
609 """
610
Stephen Warrene3f2a502016-02-03 16:46:34 -0700611 self._note("status-xpass", msg, anchor)
Stephen Warren25b05242016-01-27 23:57:51 -0700612
Stephen Warrene3f2a502016-02-03 16:46:34 -0700613 def status_fail(self, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700614 """Write a note to the log file describing failed test(s).
Stephen Warren10e50632016-01-15 11:15:24 -0700615
616 Args:
Stephen Warren25b05242016-01-27 23:57:51 -0700617 msg: A message describing the failed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700618 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700619
620 Returns:
621 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700622 """
Stephen Warren10e50632016-01-15 11:15:24 -0700623
Stephen Warrene3f2a502016-02-03 16:46:34 -0700624 self._note("status-fail", msg, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700625
626 def get_stream(self, name, chained_file=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700627 """Create an object to log a single stream's data into the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700628
629 This creates a "file-like" object that can be written to in order to
630 write a single stream's data to the log file. The implementation will
631 handle any required interleaving of data (from multiple streams) in
632 the log, in a way that makes it obvious which stream each bit of data
633 came from.
634
635 Args:
636 name: The name of the stream.
637 chained_file: The file-like object to which all stream data should
638 be logged to in addition to this log. Can be None.
639
640 Returns:
641 A file-like object.
Stephen Warren75e731e2016-01-26 13:41:30 -0700642 """
Stephen Warren10e50632016-01-15 11:15:24 -0700643
644 return LogfileStream(self, name, chained_file)
645
646 def get_runner(self, name, chained_file=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700647 """Create an object that executes processes and logs their output.
Stephen Warren10e50632016-01-15 11:15:24 -0700648
649 Args:
650 name: The name of this sub-process.
651 chained_file: The file-like object to which all stream data should
652 be logged to in addition to logfile. Can be None.
653
654 Returns:
655 A RunAndLog object.
Stephen Warren75e731e2016-01-26 13:41:30 -0700656 """
Stephen Warren10e50632016-01-15 11:15:24 -0700657
658 return RunAndLog(self, name, chained_file)
659
660 def write(self, stream, data, implicit=False):
Stephen Warren75e731e2016-01-26 13:41:30 -0700661 """Write stream data into the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700662
663 This function should only be used by instances of LogfileStream or
664 RunAndLog.
665
666 Args:
667 stream: The stream whose data is being logged.
668 data: The data to log.
669 implicit: Boolean indicating whether data actually appeared in the
670 stream, or was implicitly generated. A valid use-case is to
671 repeat a shell prompt at the start of each separate log
672 section, which makes the log sections more readable in
673 isolation.
674
675 Returns:
676 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700677 """
Stephen Warren10e50632016-01-15 11:15:24 -0700678
679 if stream != self.last_stream:
680 self._terminate_stream()
Stephen Warrene3f2a502016-02-03 16:46:34 -0700681 self.f.write('<div class="stream block">\n')
682 self.f.write('<div class="stream-header block-header">Stream: ' +
683 stream.name + '</div>\n')
684 self.f.write('<div class="stream-content block-content">\n')
Stephen Warren3deb8962016-01-26 13:41:31 -0700685 self.f.write('<pre>')
Stephen Warren10e50632016-01-15 11:15:24 -0700686 if implicit:
Stephen Warren3deb8962016-01-26 13:41:31 -0700687 self.f.write('<span class="implicit">')
Stephen Warren10e50632016-01-15 11:15:24 -0700688 self.f.write(self._escape(data))
689 if implicit:
Stephen Warren3deb8962016-01-26 13:41:31 -0700690 self.f.write('</span>')
Stephen Warren10e50632016-01-15 11:15:24 -0700691 self.last_stream = stream
692
693 def flush(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700694 """Flush the log stream, to ensure correct log interleaving.
Stephen Warren10e50632016-01-15 11:15:24 -0700695
696 Args:
697 None.
698
699 Returns:
700 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700701 """
Stephen Warren10e50632016-01-15 11:15:24 -0700702
703 self.f.flush()