blob: 8ca515319ce954e1833a4bfd0a7e2308fe48bf22 [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 Warren83357fd2016-02-03 16:46:34 -0700227
Stephen Warrena2ec5602016-01-26 13:41:31 -0700228 shutil.copy(mod_dir + '/multiplexed_log.css', os.path.dirname(fn))
229 self.f.write('''\
Stephen Warrend2015062016-01-15 11:15:24 -0700230<html>
231<head>
232<link rel="stylesheet" type="text/css" href="multiplexed_log.css">
Stephen Warren83357fd2016-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)");
255 // Hide the passed blocks
256 passed_bcs.addClass("hidden");
257 // Flip the expand/contract button hiding for those blocks.
258 bhs = passed_bcs.parent().children(".block-header")
259 bhs.children(".block-expand").removeClass("hidden");
260 bhs.children(".block-contract").addClass("hidden");
261
262 // Add click handler to block headers.
263 // The handler expands/contracts the block.
264 $(".block-header").on("click", function (e) {
265 var header = $(this);
266 var content = header.next(".block-content");
267 var expanded = !content.hasClass("hidden");
268 if (expanded) {
269 content.addClass("hidden");
270 header.children(".block-expand").first().removeClass("hidden");
271 header.children(".block-contract").first().addClass("hidden");
272 } else {
273 header.children(".block-contract").first().removeClass("hidden");
274 header.children(".block-expand").first().addClass("hidden");
275 content.removeClass("hidden");
276 }
277 });
278
279 // When clicking on a link, expand the target block
280 $("a").on("click", function (e) {
281 var block = $($(this).attr("href"));
282 var header = block.children(".block-header");
283 var content = block.children(".block-content").first();
284 header.children(".block-contract").first().removeClass("hidden");
285 header.children(".block-expand").first().addClass("hidden");
286 content.removeClass("hidden");
287 });
288});
289</script>
Stephen Warrend2015062016-01-15 11:15:24 -0700290</head>
291<body>
292<tt>
Stephen Warrena2ec5602016-01-26 13:41:31 -0700293''')
Stephen Warrend2015062016-01-15 11:15:24 -0700294
295 def close(self):
Stephen Warrene8debf32016-01-26 13:41:30 -0700296 """Close the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700297
298 After calling this function, no more data may be written to the log.
299
300 Args:
301 None.
302
303 Returns:
304 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700305 """
Stephen Warrend2015062016-01-15 11:15:24 -0700306
Stephen Warrena2ec5602016-01-26 13:41:31 -0700307 self.f.write('''\
Stephen Warrend2015062016-01-15 11:15:24 -0700308</tt>
309</body>
310</html>
Stephen Warrena2ec5602016-01-26 13:41:31 -0700311''')
Stephen Warrend2015062016-01-15 11:15:24 -0700312 self.f.close()
313
314 # The set of characters that should be represented as hexadecimal codes in
315 # the log file.
Stephen Warrena2ec5602016-01-26 13:41:31 -0700316 _nonprint = ('%' + ''.join(chr(c) for c in range(0, 32) if c not in (9, 10)) +
317 ''.join(chr(c) for c in range(127, 256)))
Stephen Warrend2015062016-01-15 11:15:24 -0700318
319 def _escape(self, data):
Stephen Warrene8debf32016-01-26 13:41:30 -0700320 """Render data format suitable for inclusion in an HTML document.
Stephen Warrend2015062016-01-15 11:15:24 -0700321
322 This includes HTML-escaping certain characters, and translating
323 control characters to a hexadecimal representation.
324
325 Args:
326 data: The raw string data to be escaped.
327
328 Returns:
329 An escaped version of the data.
Stephen Warrene8debf32016-01-26 13:41:30 -0700330 """
Stephen Warrend2015062016-01-15 11:15:24 -0700331
Stephen Warrena2ec5602016-01-26 13:41:31 -0700332 data = data.replace(chr(13), '')
333 data = ''.join((c in self._nonprint) and ('%%%02x' % ord(c)) or
Stephen Warrend2015062016-01-15 11:15:24 -0700334 c for c in data)
335 data = cgi.escape(data)
336 return data
337
338 def _terminate_stream(self):
Stephen Warrene8debf32016-01-26 13:41:30 -0700339 """Write HTML to the log file to terminate the current stream's data.
Stephen Warrend2015062016-01-15 11:15:24 -0700340
341 Args:
342 None.
343
344 Returns:
345 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700346 """
Stephen Warrend2015062016-01-15 11:15:24 -0700347
348 self.cur_evt += 1
349 if not self.last_stream:
350 return
Stephen Warrena2ec5602016-01-26 13:41:31 -0700351 self.f.write('</pre>\n')
Stephen Warren83357fd2016-02-03 16:46:34 -0700352 self.f.write('<div class="stream-trailer block-trailer">End stream: ' +
Stephen Warrena2ec5602016-01-26 13:41:31 -0700353 self.last_stream.name + '</div>\n')
354 self.f.write('</div>\n')
Stephen Warren83357fd2016-02-03 16:46:34 -0700355 self.f.write('</div>\n')
Stephen Warrend2015062016-01-15 11:15:24 -0700356 self.last_stream = None
357
Stephen Warren83357fd2016-02-03 16:46:34 -0700358 def _note(self, note_type, msg, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700359 """Write a note or one-off message to the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700360
361 Args:
362 note_type: The type of note. This must be a value supported by the
363 accompanying multiplexed_log.css.
364 msg: The note/message to log.
Stephen Warren83357fd2016-02-03 16:46:34 -0700365 anchor: Optional internal link target.
Stephen Warrend2015062016-01-15 11:15:24 -0700366
367 Returns:
368 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700369 """
Stephen Warrend2015062016-01-15 11:15:24 -0700370
371 self._terminate_stream()
Stephen Warren83357fd2016-02-03 16:46:34 -0700372 self.f.write('<div class="' + note_type + '">\n')
Stephen Warren83357fd2016-02-03 16:46:34 -0700373 self.f.write('<pre>')
Stephen Warren83357fd2016-02-03 16:46:34 -0700374 if anchor:
Stephen Warren117eeb72017-09-18 11:50:47 -0600375 self.f.write('<a href="#%s">' % anchor)
376 self.f.write(self._escape(msg))
377 if anchor:
378 self.f.write('</a>')
379 self.f.write('\n</pre>\n')
Stephen Warren83357fd2016-02-03 16:46:34 -0700380 self.f.write('</div>\n')
Stephen Warrend2015062016-01-15 11:15:24 -0700381
Stephen Warren83357fd2016-02-03 16:46:34 -0700382 def start_section(self, marker, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700383 """Begin a new nested section in the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700384
385 Args:
386 marker: The name of the section that is starting.
Stephen Warren83357fd2016-02-03 16:46:34 -0700387 anchor: The value to use for the anchor. If None, a unique value
388 will be calculated and used
Stephen Warrend2015062016-01-15 11:15:24 -0700389
390 Returns:
Stephen Warren83357fd2016-02-03 16:46:34 -0700391 Name of the HTML anchor emitted before section.
Stephen Warrene8debf32016-01-26 13:41:30 -0700392 """
Stephen Warrend2015062016-01-15 11:15:24 -0700393
394 self._terminate_stream()
395 self.blocks.append(marker)
Stephen Warren9679d332017-10-27 11:04:08 -0600396 self.timestamp_blocks.append(self._get_time())
Stephen Warren83357fd2016-02-03 16:46:34 -0700397 if not anchor:
398 self.anchor += 1
399 anchor = str(self.anchor)
Stephen Warrena2ec5602016-01-26 13:41:31 -0700400 blk_path = '/'.join(self.blocks)
Stephen Warren83357fd2016-02-03 16:46:34 -0700401 self.f.write('<div class="section block" id="' + anchor + '">\n')
402 self.f.write('<div class="section-header block-header">Section: ' +
403 blk_path + '</div>\n')
404 self.f.write('<div class="section-content block-content">\n')
Stephen Warren9679d332017-10-27 11:04:08 -0600405 self.timestamp()
Stephen Warren83357fd2016-02-03 16:46:34 -0700406
407 return anchor
Stephen Warrend2015062016-01-15 11:15:24 -0700408
409 def end_section(self, marker):
Stephen Warrene8debf32016-01-26 13:41:30 -0700410 """Terminate the current nested section in the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700411
412 This function validates proper nesting of start_section() and
413 end_section() calls. If a mismatch is found, an exception is raised.
414
415 Args:
416 marker: The name of the section that is ending.
417
418 Returns:
419 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700420 """
Stephen Warrend2015062016-01-15 11:15:24 -0700421
422 if (not self.blocks) or (marker != self.blocks[-1]):
Stephen Warrena2ec5602016-01-26 13:41:31 -0700423 raise Exception('Block nesting mismatch: "%s" "%s"' %
424 (marker, '/'.join(self.blocks)))
Stephen Warrend2015062016-01-15 11:15:24 -0700425 self._terminate_stream()
Stephen Warren9679d332017-10-27 11:04:08 -0600426 timestamp_now = self._get_time()
427 timestamp_section_start = self.timestamp_blocks.pop()
428 delta_section = timestamp_now - timestamp_section_start
429 self._note("timestamp",
430 "TIME: SINCE-SECTION: " + str(delta_section))
Stephen Warrena2ec5602016-01-26 13:41:31 -0700431 blk_path = '/'.join(self.blocks)
Stephen Warren83357fd2016-02-03 16:46:34 -0700432 self.f.write('<div class="section-trailer block-trailer">' +
433 'End section: ' + blk_path + '</div>\n')
434 self.f.write('</div>\n')
Stephen Warrena2ec5602016-01-26 13:41:31 -0700435 self.f.write('</div>\n')
Stephen Warrend2015062016-01-15 11:15:24 -0700436 self.blocks.pop()
437
Stephen Warren83357fd2016-02-03 16:46:34 -0700438 def section(self, marker, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700439 """Create a temporary section in the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700440
441 This function creates a context manager for Python's "with" statement,
442 which allows a certain portion of test code to be logged to a separate
443 section of the log file.
444
445 Usage:
446 with log.section("somename"):
447 some test code
448
449 Args:
450 marker: The name of the nested section.
Stephen Warren83357fd2016-02-03 16:46:34 -0700451 anchor: The anchor value to pass to start_section().
Stephen Warrend2015062016-01-15 11:15:24 -0700452
453 Returns:
454 A context manager object.
Stephen Warrene8debf32016-01-26 13:41:30 -0700455 """
Stephen Warrend2015062016-01-15 11:15:24 -0700456
Stephen Warren83357fd2016-02-03 16:46:34 -0700457 return SectionCtxMgr(self, marker, anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700458
459 def error(self, msg):
Stephen Warrene8debf32016-01-26 13:41:30 -0700460 """Write an error note to the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700461
462 Args:
463 msg: A message describing the error.
464
465 Returns:
466 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700467 """
Stephen Warrend2015062016-01-15 11:15:24 -0700468
469 self._note("error", msg)
470
471 def warning(self, msg):
Stephen Warrene8debf32016-01-26 13:41:30 -0700472 """Write an warning note to the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700473
474 Args:
475 msg: A message describing the warning.
476
477 Returns:
478 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700479 """
Stephen Warrend2015062016-01-15 11:15:24 -0700480
481 self._note("warning", msg)
482
483 def info(self, msg):
Stephen Warrene8debf32016-01-26 13:41:30 -0700484 """Write an informational note to the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700485
486 Args:
487 msg: An informational message.
488
489 Returns:
490 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700491 """
Stephen Warrend2015062016-01-15 11:15:24 -0700492
493 self._note("info", msg)
494
495 def action(self, msg):
Stephen Warrene8debf32016-01-26 13:41:30 -0700496 """Write an action note to the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700497
498 Args:
499 msg: A message describing the action that is being logged.
500
501 Returns:
502 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700503 """
Stephen Warrend2015062016-01-15 11:15:24 -0700504
505 self._note("action", msg)
506
Stephen Warren9679d332017-10-27 11:04:08 -0600507 def _get_time(self):
508 return datetime.datetime.now()
509
510 def timestamp(self):
511 """Write a timestamp to the log file.
512
513 Args:
514 None
515
516 Returns:
517 Nothing.
518 """
519
520 timestamp_now = self._get_time()
521 delta_prev = timestamp_now - self.timestamp_prev
522 delta_start = timestamp_now - self.timestamp_start
523 self.timestamp_prev = timestamp_now
524
525 self._note("timestamp",
526 "TIME: NOW: " + timestamp_now.strftime("%Y/%m/%d %H:%M:%S.%f"))
527 self._note("timestamp",
528 "TIME: SINCE-PREV: " + str(delta_prev))
529 self._note("timestamp",
530 "TIME: SINCE-START: " + str(delta_start))
531
Stephen Warren83357fd2016-02-03 16:46:34 -0700532 def status_pass(self, msg, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700533 """Write a note to the log file describing test(s) which passed.
Stephen Warrend2015062016-01-15 11:15:24 -0700534
535 Args:
Stephen Warren78b39cc2016-01-27 23:57:51 -0700536 msg: A message describing the passed test(s).
Stephen Warren83357fd2016-02-03 16:46:34 -0700537 anchor: Optional internal link target.
Stephen Warrend2015062016-01-15 11:15:24 -0700538
539 Returns:
540 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700541 """
Stephen Warrend2015062016-01-15 11:15:24 -0700542
Stephen Warren83357fd2016-02-03 16:46:34 -0700543 self._note("status-pass", msg, anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700544
Stephen Warren83357fd2016-02-03 16:46:34 -0700545 def status_skipped(self, msg, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700546 """Write a note to the log file describing skipped test(s).
Stephen Warrend2015062016-01-15 11:15:24 -0700547
548 Args:
Stephen Warren78b39cc2016-01-27 23:57:51 -0700549 msg: A message describing the skipped test(s).
Stephen Warren83357fd2016-02-03 16:46:34 -0700550 anchor: Optional internal link target.
Stephen Warrend2015062016-01-15 11:15:24 -0700551
552 Returns:
553 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700554 """
Stephen Warrend2015062016-01-15 11:15:24 -0700555
Stephen Warren83357fd2016-02-03 16:46:34 -0700556 self._note("status-skipped", msg, anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700557
Stephen Warren83357fd2016-02-03 16:46:34 -0700558 def status_xfail(self, msg, anchor=None):
Stephen Warren78b39cc2016-01-27 23:57:51 -0700559 """Write a note to the log file describing xfailed test(s).
560
561 Args:
562 msg: A message describing the xfailed test(s).
Stephen Warren83357fd2016-02-03 16:46:34 -0700563 anchor: Optional internal link target.
Stephen Warren78b39cc2016-01-27 23:57:51 -0700564
565 Returns:
566 Nothing.
567 """
568
Stephen Warren83357fd2016-02-03 16:46:34 -0700569 self._note("status-xfail", msg, anchor)
Stephen Warren78b39cc2016-01-27 23:57:51 -0700570
Stephen Warren83357fd2016-02-03 16:46:34 -0700571 def status_xpass(self, msg, anchor=None):
Stephen Warren78b39cc2016-01-27 23:57:51 -0700572 """Write a note to the log file describing xpassed test(s).
573
574 Args:
575 msg: A message describing the xpassed test(s).
Stephen Warren83357fd2016-02-03 16:46:34 -0700576 anchor: Optional internal link target.
Stephen Warren78b39cc2016-01-27 23:57:51 -0700577
578 Returns:
579 Nothing.
580 """
581
Stephen Warren83357fd2016-02-03 16:46:34 -0700582 self._note("status-xpass", msg, anchor)
Stephen Warren78b39cc2016-01-27 23:57:51 -0700583
Stephen Warren83357fd2016-02-03 16:46:34 -0700584 def status_fail(self, msg, anchor=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700585 """Write a note to the log file describing failed test(s).
Stephen Warrend2015062016-01-15 11:15:24 -0700586
587 Args:
Stephen Warren78b39cc2016-01-27 23:57:51 -0700588 msg: A message describing the failed test(s).
Stephen Warren83357fd2016-02-03 16:46:34 -0700589 anchor: Optional internal link target.
Stephen Warrend2015062016-01-15 11:15:24 -0700590
591 Returns:
592 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700593 """
Stephen Warrend2015062016-01-15 11:15:24 -0700594
Stephen Warren83357fd2016-02-03 16:46:34 -0700595 self._note("status-fail", msg, anchor)
Stephen Warrend2015062016-01-15 11:15:24 -0700596
597 def get_stream(self, name, chained_file=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700598 """Create an object to log a single stream's data into the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700599
600 This creates a "file-like" object that can be written to in order to
601 write a single stream's data to the log file. The implementation will
602 handle any required interleaving of data (from multiple streams) in
603 the log, in a way that makes it obvious which stream each bit of data
604 came from.
605
606 Args:
607 name: The name of the stream.
608 chained_file: The file-like object to which all stream data should
609 be logged to in addition to this log. Can be None.
610
611 Returns:
612 A file-like object.
Stephen Warrene8debf32016-01-26 13:41:30 -0700613 """
Stephen Warrend2015062016-01-15 11:15:24 -0700614
615 return LogfileStream(self, name, chained_file)
616
617 def get_runner(self, name, chained_file=None):
Stephen Warrene8debf32016-01-26 13:41:30 -0700618 """Create an object that executes processes and logs their output.
Stephen Warrend2015062016-01-15 11:15:24 -0700619
620 Args:
621 name: The name of this sub-process.
622 chained_file: The file-like object to which all stream data should
623 be logged to in addition to logfile. Can be None.
624
625 Returns:
626 A RunAndLog object.
Stephen Warrene8debf32016-01-26 13:41:30 -0700627 """
Stephen Warrend2015062016-01-15 11:15:24 -0700628
629 return RunAndLog(self, name, chained_file)
630
631 def write(self, stream, data, implicit=False):
Stephen Warrene8debf32016-01-26 13:41:30 -0700632 """Write stream data into the log file.
Stephen Warrend2015062016-01-15 11:15:24 -0700633
634 This function should only be used by instances of LogfileStream or
635 RunAndLog.
636
637 Args:
638 stream: The stream whose data is being logged.
639 data: The data to log.
640 implicit: Boolean indicating whether data actually appeared in the
641 stream, or was implicitly generated. A valid use-case is to
642 repeat a shell prompt at the start of each separate log
643 section, which makes the log sections more readable in
644 isolation.
645
646 Returns:
647 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700648 """
Stephen Warrend2015062016-01-15 11:15:24 -0700649
650 if stream != self.last_stream:
651 self._terminate_stream()
Stephen Warren83357fd2016-02-03 16:46:34 -0700652 self.f.write('<div class="stream block">\n')
653 self.f.write('<div class="stream-header block-header">Stream: ' +
654 stream.name + '</div>\n')
655 self.f.write('<div class="stream-content block-content">\n')
Stephen Warrena2ec5602016-01-26 13:41:31 -0700656 self.f.write('<pre>')
Stephen Warrend2015062016-01-15 11:15:24 -0700657 if implicit:
Stephen Warrena2ec5602016-01-26 13:41:31 -0700658 self.f.write('<span class="implicit">')
Stephen Warrend2015062016-01-15 11:15:24 -0700659 self.f.write(self._escape(data))
660 if implicit:
Stephen Warrena2ec5602016-01-26 13:41:31 -0700661 self.f.write('</span>')
Stephen Warrend2015062016-01-15 11:15:24 -0700662 self.last_stream = stream
663
664 def flush(self):
Stephen Warrene8debf32016-01-26 13:41:30 -0700665 """Flush the log stream, to ensure correct log interleaving.
Stephen Warrend2015062016-01-15 11:15:24 -0700666
667 Args:
668 None.
669
670 Returns:
671 Nothing.
Stephen Warrene8debf32016-01-26 13:41:30 -0700672 """
Stephen Warrend2015062016-01-15 11:15:24 -0700673
674 self.f.flush()