blob: a2cfd71746190f9e54cfc8f8f2ba40132be80e1d [file] [log] [blame]
Stephen Warrend2015062016-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 Warren9679d332017-10-27 11:04:08 -060010import datetime
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -070018 """A file-like object used to write a single logical stream of data into
Stephen Warrend2015062016-01-15 11:15:24 -070019 a multiplexed log file. Objects of this type should be created by factory
Stephen Warrene8debf32016-01-26 13:41:30 -070020 functions in the Logfile class rather than directly."""
Stephen Warrend2015062016-01-15 11:15:24 -070021
22 def __init__(self, logfile, name, chained_file):
Stephen Warrene8debf32016-01-26 13:41:30 -070023 """Initialize a new object.
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -070033 """
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -070040 """Dummy function so that this class is "file-like".
Stephen Warrend2015062016-01-15 11:15:24 -070041
42 Args:
43 None.
44
45 Returns:
46 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -070047 """
Stephen Warrend2015062016-01-15 11:15:24 -070048
49 pass
50
51 def write(self, data, implicit=False):
Stephen Warrene8debf32016-01-26 13:41:30 -070052 """Write data to the log stream.
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -070064 """
Stephen Warrend2015062016-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 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()
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 Warren3f2faf72016-01-22 12:30:11 -0700161 if exit_status and not exception and not ignore_errors:
Stephen Warrend2015062016-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 Warren9679d332017-10-27 11:04:08 -0600168 self.logfile.timestamp()
Simon Glass86845bf2016-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 Glass7f64b182016-07-31 17:35:03 -0600172 self.exit_status = exit_status
Stephen Warrend2015062016-01-15 11:15:24 -0700173 if exception:
174 raise exception
Simon Glass3b8d9d92016-07-03 09:40:37 -0600175 return output
Stephen Warrend2015062016-01-15 11:15:24 -0700176
177class SectionCtxMgr(object):
Stephen Warrene8debf32016-01-26 13:41:30 -0700178 """A context manager for Python's "with" statement, which allows a certain
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -0700181 class rather than directly."""
Stephen Warrend2015062016-01-15 11:15:24 -0700182
Stephen Warren83357fd2016-02-03 16:46:34 -0700183 def __init__(self, log, marker, anchor):
Stephen Warrene8debf32016-01-26 13:41:30 -0700184 """Initialize a new object.
Stephen Warrend2015062016-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 Warren83357fd2016-02-03 16:46:34 -0700189 anchor: The anchor value to pass to start_section().
Stephen Warrend2015062016-01-15 11:15:24 -0700190
191 Returns:
192 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700193 """
Stephen Warrend2015062016-01-15 11:15:24 -0700194
195 self.log = log
196 self.marker = marker
Stephen Warren83357fd2016-02-03 16:46:34 -0700197 self.anchor = anchor
Stephen Warrend2015062016-01-15 11:15:24 -0700198
199 def __enter__(self):
Stephen Warren83357fd2016-02-03 16:46:34 -0700200 self.anchor = self.log.start_section(self.marker, self.anchor)
Stephen Warrend2015062016-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 Warrene8debf32016-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 Warrend2015062016-01-15 11:15:24 -0700208
209 def __init__(self, fn):
Stephen Warrene8debf32016-01-26 13:41:30 -0700210 """Initialize a new object.
Stephen Warrend2015062016-01-15 11:15:24 -0700211
212 Args:
213 fn: The filename to write to.
214
215 Returns:
216 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700217 """
Stephen Warrend2015062016-01-15 11:15:24 -0700218
Stephen Warrena2ec5602016-01-26 13:41:31 -0700219 self.f = open(fn, 'wt')
Stephen Warrend2015062016-01-15 11:15:24 -0700220 self.last_stream = None
221 self.blocks = []
222 self.cur_evt = 1
Stephen Warren83357fd2016-02-03 16:46:34 -0700223 self.anchor = 0
Stephen Warren9679d332017-10-27 11:04:08 -0600224 self.timestamp_start = self._get_time()
225 self.timestamp_prev = self.timestamp_start
226 self.timestamp_blocks = []
Stephen Warren32090e52018-02-20 12:51:55 -0700227 self.seen_warning = False
Stephen Warren83357fd2016-02-03 16:46:34 -0700228
Stephen Warrena2ec5602016-01-26 13:41:31 -0700229 shutil.copy(mod_dir + '/multiplexed_log.css', os.path.dirname(fn))
230 self.f.write('''\
Stephen Warrend2015062016-01-15 11:15:24 -0700231<html>
232<head>
233<link rel="stylesheet" type="text/css" href="multiplexed_log.css">
Stephen Warren83357fd2016-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 Warren32090e52018-02-20 12:51:55 -0700256 passed_bcs = passed_bcs.not(":has(.status-warning)");
Stephen Warren83357fd2016-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 Warrend2015062016-01-15 11:15:24 -0700292</head>
293<body>
294<tt>
Stephen Warrena2ec5602016-01-26 13:41:31 -0700295''')
Stephen Warrend2015062016-01-15 11:15:24 -0700296
297 def close(self):
Stephen Warrene8debf32016-01-26 13:41:30 -0700298 """Close the log file.
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -0700307 """
Stephen Warrend2015062016-01-15 11:15:24 -0700308
Stephen Warrena2ec5602016-01-26 13:41:31 -0700309 self.f.write('''\
Stephen Warrend2015062016-01-15 11:15:24 -0700310</tt>
311</body>
312</html>
Stephen Warrena2ec5602016-01-26 13:41:31 -0700313''')
Stephen Warrend2015062016-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 Warrena2ec5602016-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 Warrend2015062016-01-15 11:15:24 -0700320
321 def _escape(self, data):
Stephen Warrene8debf32016-01-26 13:41:30 -0700322 """Render data format suitable for inclusion in an HTML document.
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -0700332 """
Stephen Warrend2015062016-01-15 11:15:24 -0700333
Stephen Warrena2ec5602016-01-26 13:41:31 -0700334 data = data.replace(chr(13), '')
335 data = ''.join((c in self._nonprint) and ('%%%02x' % ord(c)) or
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -0700341 """Write HTML to the log file to terminate the current stream's data.
Stephen Warrend2015062016-01-15 11:15:24 -0700342
343 Args:
344 None.
345
346 Returns:
347 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700348 """
Stephen Warrend2015062016-01-15 11:15:24 -0700349
350 self.cur_evt += 1
351 if not self.last_stream:
352 return
Stephen Warrena2ec5602016-01-26 13:41:31 -0700353 self.f.write('</pre>\n')
Stephen Warren83357fd2016-02-03 16:46:34 -0700354 self.f.write('<div class="stream-trailer block-trailer">End stream: ' +
Stephen Warrena2ec5602016-01-26 13:41:31 -0700355 self.last_stream.name + '</div>\n')
356 self.f.write('</div>\n')
Stephen Warren83357fd2016-02-03 16:46:34 -0700357 self.f.write('</div>\n')
Stephen Warrend2015062016-01-15 11:15:24 -0700358 self.last_stream = None
359
Stephen Warren83357fd2016-02-03 16:46:34 -0700360 def _note(self, note_type, msg, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700361 """Write a note or one-off message to the log file.
Stephen Warrend2015062016-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 Warren83357fd2016-02-03 16:46:34 -0700367 anchor: Optional internal link target.
Stephen Warrend2015062016-01-15 11:15:24 -0700368
369 Returns:
370 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700371 """
Stephen Warrend2015062016-01-15 11:15:24 -0700372
373 self._terminate_stream()
Stephen Warren83357fd2016-02-03 16:46:34 -0700374 self.f.write('<div class="' + note_type + '">\n')
Stephen Warren83357fd2016-02-03 16:46:34 -0700375 self.f.write('<pre>')
Stephen Warren83357fd2016-02-03 16:46:34 -0700376 if anchor:
Stephen Warren117eeb72017-09-18 11:50:47 -0600377 self.f.write('<a href="#%s">' % anchor)
378 self.f.write(self._escape(msg))
379 if anchor:
380 self.f.write('</a>')
381 self.f.write('\n</pre>\n')
Stephen Warren83357fd2016-02-03 16:46:34 -0700382 self.f.write('</div>\n')
Stephen Warrend2015062016-01-15 11:15:24 -0700383
Stephen Warren83357fd2016-02-03 16:46:34 -0700384 def start_section(self, marker, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700385 """Begin a new nested section in the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700386
387 Args:
388 marker: The name of the section that is starting.
Stephen Warren83357fd2016-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 Warrend2015062016-01-15 11:15:24 -0700391
392 Returns:
Stephen Warren83357fd2016-02-03 16:46:34 -0700393 Name of the HTML anchor emitted before section.
Stephen Warrene8debf32016-01-26 13:41:30 -0700394 """
Stephen Warrend2015062016-01-15 11:15:24 -0700395
396 self._terminate_stream()
397 self.blocks.append(marker)
Stephen Warren9679d332017-10-27 11:04:08 -0600398 self.timestamp_blocks.append(self._get_time())
Stephen Warren83357fd2016-02-03 16:46:34 -0700399 if not anchor:
400 self.anchor += 1
401 anchor = str(self.anchor)
Stephen Warrena2ec5602016-01-26 13:41:31 -0700402 blk_path = '/'.join(self.blocks)
Stephen Warren83357fd2016-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 Warren9679d332017-10-27 11:04:08 -0600407 self.timestamp()
Stephen Warren83357fd2016-02-03 16:46:34 -0700408
409 return anchor
Stephen Warrend2015062016-01-15 11:15:24 -0700410
411 def end_section(self, marker):
Stephen Warrene8debf32016-01-26 13:41:30 -0700412 """Terminate the current nested section in the log file.
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -0700422 """
Stephen Warrend2015062016-01-15 11:15:24 -0700423
424 if (not self.blocks) or (marker != self.blocks[-1]):
Stephen Warrena2ec5602016-01-26 13:41:31 -0700425 raise Exception('Block nesting mismatch: "%s" "%s"' %
426 (marker, '/'.join(self.blocks)))
Stephen Warrend2015062016-01-15 11:15:24 -0700427 self._terminate_stream()
Stephen Warren9679d332017-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 Warrena2ec5602016-01-26 13:41:31 -0700433 blk_path = '/'.join(self.blocks)
Stephen Warren83357fd2016-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 Warrena2ec5602016-01-26 13:41:31 -0700437 self.f.write('</div>\n')
Stephen Warrend2015062016-01-15 11:15:24 -0700438 self.blocks.pop()
439
Stephen Warren83357fd2016-02-03 16:46:34 -0700440 def section(self, marker, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700441 """Create a temporary section in the log file.
Stephen Warrend2015062016-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 Warren83357fd2016-02-03 16:46:34 -0700453 anchor: The anchor value to pass to start_section().
Stephen Warrend2015062016-01-15 11:15:24 -0700454
455 Returns:
456 A context manager object.
Stephen Warrene8debf32016-01-26 13:41:30 -0700457 """
Stephen Warrend2015062016-01-15 11:15:24 -0700458
Stephen Warren83357fd2016-02-03 16:46:34 -0700459 return SectionCtxMgr(self, marker, anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700460
461 def error(self, msg):
Stephen Warrene8debf32016-01-26 13:41:30 -0700462 """Write an error note to the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700463
464 Args:
465 msg: A message describing the error.
466
467 Returns:
468 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700469 """
Stephen Warrend2015062016-01-15 11:15:24 -0700470
471 self._note("error", msg)
472
473 def warning(self, msg):
Stephen Warrene8debf32016-01-26 13:41:30 -0700474 """Write an warning note to the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700475
476 Args:
477 msg: A message describing the warning.
478
479 Returns:
480 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700481 """
Stephen Warrend2015062016-01-15 11:15:24 -0700482
Stephen Warren32090e52018-02-20 12:51:55 -0700483 self.seen_warning = True
Stephen Warrend2015062016-01-15 11:15:24 -0700484 self._note("warning", msg)
485
Stephen Warren32090e52018-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 Warrend2015062016-01-15 11:15:24 -0700500 def info(self, msg):
Stephen Warrene8debf32016-01-26 13:41:30 -0700501 """Write an informational note to the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700502
503 Args:
504 msg: An informational message.
505
506 Returns:
507 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700508 """
Stephen Warrend2015062016-01-15 11:15:24 -0700509
510 self._note("info", msg)
511
512 def action(self, msg):
Stephen Warrene8debf32016-01-26 13:41:30 -0700513 """Write an action note to the log file.
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -0700520 """
Stephen Warrend2015062016-01-15 11:15:24 -0700521
522 self._note("action", msg)
523
Stephen Warren9679d332017-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 Warren83357fd2016-02-03 16:46:34 -0700549 def status_pass(self, msg, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700550 """Write a note to the log file describing test(s) which passed.
Stephen Warrend2015062016-01-15 11:15:24 -0700551
552 Args:
Stephen Warren78b39cc2016-01-27 23:57:51 -0700553 msg: A message describing the passed test(s).
Stephen Warren83357fd2016-02-03 16:46:34 -0700554 anchor: Optional internal link target.
Stephen Warrend2015062016-01-15 11:15:24 -0700555
556 Returns:
557 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700558 """
Stephen Warrend2015062016-01-15 11:15:24 -0700559
Stephen Warren83357fd2016-02-03 16:46:34 -0700560 self._note("status-pass", msg, anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700561
Stephen Warren32090e52018-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 Warren83357fd2016-02-03 16:46:34 -0700575 def status_skipped(self, msg, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700576 """Write a note to the log file describing skipped test(s).
Stephen Warrend2015062016-01-15 11:15:24 -0700577
578 Args:
Stephen Warren78b39cc2016-01-27 23:57:51 -0700579 msg: A message describing the skipped test(s).
Stephen Warren83357fd2016-02-03 16:46:34 -0700580 anchor: Optional internal link target.
Stephen Warrend2015062016-01-15 11:15:24 -0700581
582 Returns:
583 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700584 """
Stephen Warrend2015062016-01-15 11:15:24 -0700585
Stephen Warren83357fd2016-02-03 16:46:34 -0700586 self._note("status-skipped", msg, anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700587
Stephen Warren83357fd2016-02-03 16:46:34 -0700588 def status_xfail(self, msg, anchor=None):
Stephen Warren78b39cc2016-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 Warren83357fd2016-02-03 16:46:34 -0700593 anchor: Optional internal link target.
Stephen Warren78b39cc2016-01-27 23:57:51 -0700594
595 Returns:
596 Nothing.
597 """
598
Stephen Warren83357fd2016-02-03 16:46:34 -0700599 self._note("status-xfail", msg, anchor)
Stephen Warren78b39cc2016-01-27 23:57:51 -0700600
Stephen Warren83357fd2016-02-03 16:46:34 -0700601 def status_xpass(self, msg, anchor=None):
Stephen Warren78b39cc2016-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 Warren83357fd2016-02-03 16:46:34 -0700606 anchor: Optional internal link target.
Stephen Warren78b39cc2016-01-27 23:57:51 -0700607
608 Returns:
609 Nothing.
610 """
611
Stephen Warren83357fd2016-02-03 16:46:34 -0700612 self._note("status-xpass", msg, anchor)
Stephen Warren78b39cc2016-01-27 23:57:51 -0700613
Stephen Warren83357fd2016-02-03 16:46:34 -0700614 def status_fail(self, msg, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700615 """Write a note to the log file describing failed test(s).
Stephen Warrend2015062016-01-15 11:15:24 -0700616
617 Args:
Stephen Warren78b39cc2016-01-27 23:57:51 -0700618 msg: A message describing the failed test(s).
Stephen Warren83357fd2016-02-03 16:46:34 -0700619 anchor: Optional internal link target.
Stephen Warrend2015062016-01-15 11:15:24 -0700620
621 Returns:
622 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700623 """
Stephen Warrend2015062016-01-15 11:15:24 -0700624
Stephen Warren83357fd2016-02-03 16:46:34 -0700625 self._note("status-fail", msg, anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700626
627 def get_stream(self, name, chained_file=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700628 """Create an object to log a single stream's data into the log file.
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -0700643 """
Stephen Warrend2015062016-01-15 11:15:24 -0700644
645 return LogfileStream(self, name, chained_file)
646
647 def get_runner(self, name, chained_file=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700648 """Create an object that executes processes and logs their output.
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -0700657 """
Stephen Warrend2015062016-01-15 11:15:24 -0700658
659 return RunAndLog(self, name, chained_file)
660
661 def write(self, stream, data, implicit=False):
Stephen Warrene8debf32016-01-26 13:41:30 -0700662 """Write stream data into the log file.
Stephen Warrend2015062016-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 Warrene8debf32016-01-26 13:41:30 -0700678 """
Stephen Warrend2015062016-01-15 11:15:24 -0700679
680 if stream != self.last_stream:
681 self._terminate_stream()
Stephen Warren83357fd2016-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 Warrena2ec5602016-01-26 13:41:31 -0700686 self.f.write('<pre>')
Stephen Warrend2015062016-01-15 11:15:24 -0700687 if implicit:
Stephen Warrena2ec5602016-01-26 13:41:31 -0700688 self.f.write('<span class="implicit">')
Stephen Warrend2015062016-01-15 11:15:24 -0700689 self.f.write(self._escape(data))
690 if implicit:
Stephen Warrena2ec5602016-01-26 13:41:31 -0700691 self.f.write('</span>')
Stephen Warrend2015062016-01-15 11:15:24 -0700692 self.last_stream = stream
693
694 def flush(self):
Stephen Warrene8debf32016-01-26 13:41:30 -0700695 """Flush the log stream, to ensure correct log interleaving.
Stephen Warrend2015062016-01-15 11:15:24 -0700696
697 Args:
698 None.
699
700 Returns:
701 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700702 """
Stephen Warrend2015062016-01-15 11:15:24 -0700703
704 self.f.flush()