blob: 637a3bd257ba3855604b446e94f5d571452accbc [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.
Simon Glassb6b30652018-10-01 21:12:34 -0600317 _nonprint = {ord('%')}
318 _nonprint.update({c for c in range(0, 32) if c not in (9, 10)})
319 _nonprint.update({c for c in range(127, 256)})
Stephen Warren10e50632016-01-15 11:15:24 -0700320
321 def _escape(self, data):
Stephen Warren75e731e2016-01-26 13:41:30 -0700322 """Render data format suitable for inclusion in an HTML document.
Stephen Warren10e50632016-01-15 11:15:24 -0700323
324 This includes HTML-escaping certain characters, and translating
325 control characters to a hexadecimal representation.
326
327 Args:
328 data: The raw string data to be escaped.
329
330 Returns:
331 An escaped version of the data.
Stephen Warren75e731e2016-01-26 13:41:30 -0700332 """
Stephen Warren10e50632016-01-15 11:15:24 -0700333
Stephen Warren3deb8962016-01-26 13:41:31 -0700334 data = data.replace(chr(13), '')
Simon Glassb6b30652018-10-01 21:12:34 -0600335 data = ''.join((ord(c) in self._nonprint) and ('%%%02x' % ord(c)) or
Stephen Warren10e50632016-01-15 11:15:24 -0700336 c for c in data)
337 data = cgi.escape(data)
338 return data
339
340 def _terminate_stream(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700341 """Write HTML to the log file to terminate the current stream's data.
Stephen Warren10e50632016-01-15 11:15:24 -0700342
343 Args:
344 None.
345
346 Returns:
347 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700348 """
Stephen Warren10e50632016-01-15 11:15:24 -0700349
350 self.cur_evt += 1
351 if not self.last_stream:
352 return
Stephen Warren3deb8962016-01-26 13:41:31 -0700353 self.f.write('</pre>\n')
Stephen Warrene3f2a502016-02-03 16:46:34 -0700354 self.f.write('<div class="stream-trailer block-trailer">End stream: ' +
Stephen Warren3deb8962016-01-26 13:41:31 -0700355 self.last_stream.name + '</div>\n')
356 self.f.write('</div>\n')
Stephen Warrene3f2a502016-02-03 16:46:34 -0700357 self.f.write('</div>\n')
Stephen Warren10e50632016-01-15 11:15:24 -0700358 self.last_stream = None
359
Stephen Warrene3f2a502016-02-03 16:46:34 -0700360 def _note(self, note_type, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700361 """Write a note or one-off message to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700362
363 Args:
364 note_type: The type of note. This must be a value supported by the
365 accompanying multiplexed_log.css.
366 msg: The note/message to log.
Stephen Warrene3f2a502016-02-03 16:46:34 -0700367 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700368
369 Returns:
370 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700371 """
Stephen Warren10e50632016-01-15 11:15:24 -0700372
373 self._terminate_stream()
Stephen Warrene3f2a502016-02-03 16:46:34 -0700374 self.f.write('<div class="' + note_type + '">\n')
Stephen Warrene3f2a502016-02-03 16:46:34 -0700375 self.f.write('<pre>')
Stephen Warrene0d2aee2017-09-18 11:50:47 -0600376 if anchor:
377 self.f.write('<a href="#%s">' % anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700378 self.f.write(self._escape(msg))
Stephen Warrene3f2a502016-02-03 16:46:34 -0700379 if anchor:
Stephen Warrene0d2aee2017-09-18 11:50:47 -0600380 self.f.write('</a>')
381 self.f.write('\n</pre>\n')
Stephen Warrene3f2a502016-02-03 16:46:34 -0700382 self.f.write('</div>\n')
Stephen Warren10e50632016-01-15 11:15:24 -0700383
Stephen Warrene3f2a502016-02-03 16:46:34 -0700384 def start_section(self, marker, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700385 """Begin a new nested section in the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700386
387 Args:
388 marker: The name of the section that is starting.
Stephen Warrene3f2a502016-02-03 16:46:34 -0700389 anchor: The value to use for the anchor. If None, a unique value
390 will be calculated and used
Stephen Warren10e50632016-01-15 11:15:24 -0700391
392 Returns:
Stephen Warrene3f2a502016-02-03 16:46:34 -0700393 Name of the HTML anchor emitted before section.
Stephen Warren75e731e2016-01-26 13:41:30 -0700394 """
Stephen Warren10e50632016-01-15 11:15:24 -0700395
396 self._terminate_stream()
397 self.blocks.append(marker)
Stephen Warrenb1c556a2017-10-27 11:04:08 -0600398 self.timestamp_blocks.append(self._get_time())
Stephen Warrene3f2a502016-02-03 16:46:34 -0700399 if not anchor:
400 self.anchor += 1
401 anchor = str(self.anchor)
Stephen Warren3deb8962016-01-26 13:41:31 -0700402 blk_path = '/'.join(self.blocks)
Stephen Warrene3f2a502016-02-03 16:46:34 -0700403 self.f.write('<div class="section block" id="' + anchor + '">\n')
404 self.f.write('<div class="section-header block-header">Section: ' +
405 blk_path + '</div>\n')
406 self.f.write('<div class="section-content block-content">\n')
Stephen Warrenb1c556a2017-10-27 11:04:08 -0600407 self.timestamp()
Stephen Warrene3f2a502016-02-03 16:46:34 -0700408
409 return anchor
Stephen Warren10e50632016-01-15 11:15:24 -0700410
411 def end_section(self, marker):
Stephen Warren75e731e2016-01-26 13:41:30 -0700412 """Terminate the current nested section in the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700413
414 This function validates proper nesting of start_section() and
415 end_section() calls. If a mismatch is found, an exception is raised.
416
417 Args:
418 marker: The name of the section that is ending.
419
420 Returns:
421 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700422 """
Stephen Warren10e50632016-01-15 11:15:24 -0700423
424 if (not self.blocks) or (marker != self.blocks[-1]):
Stephen Warren3deb8962016-01-26 13:41:31 -0700425 raise Exception('Block nesting mismatch: "%s" "%s"' %
426 (marker, '/'.join(self.blocks)))
Stephen Warren10e50632016-01-15 11:15:24 -0700427 self._terminate_stream()
Stephen Warrenb1c556a2017-10-27 11:04:08 -0600428 timestamp_now = self._get_time()
429 timestamp_section_start = self.timestamp_blocks.pop()
430 delta_section = timestamp_now - timestamp_section_start
431 self._note("timestamp",
432 "TIME: SINCE-SECTION: " + str(delta_section))
Stephen Warren3deb8962016-01-26 13:41:31 -0700433 blk_path = '/'.join(self.blocks)
Stephen Warrene3f2a502016-02-03 16:46:34 -0700434 self.f.write('<div class="section-trailer block-trailer">' +
435 'End section: ' + blk_path + '</div>\n')
436 self.f.write('</div>\n')
Stephen Warren3deb8962016-01-26 13:41:31 -0700437 self.f.write('</div>\n')
Stephen Warren10e50632016-01-15 11:15:24 -0700438 self.blocks.pop()
439
Stephen Warrene3f2a502016-02-03 16:46:34 -0700440 def section(self, marker, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700441 """Create a temporary section in the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700442
443 This function creates a context manager for Python's "with" statement,
444 which allows a certain portion of test code to be logged to a separate
445 section of the log file.
446
447 Usage:
448 with log.section("somename"):
449 some test code
450
451 Args:
452 marker: The name of the nested section.
Stephen Warrene3f2a502016-02-03 16:46:34 -0700453 anchor: The anchor value to pass to start_section().
Stephen Warren10e50632016-01-15 11:15:24 -0700454
455 Returns:
456 A context manager object.
Stephen Warren75e731e2016-01-26 13:41:30 -0700457 """
Stephen Warren10e50632016-01-15 11:15:24 -0700458
Stephen Warrene3f2a502016-02-03 16:46:34 -0700459 return SectionCtxMgr(self, marker, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700460
461 def error(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700462 """Write an error note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700463
464 Args:
465 msg: A message describing the error.
466
467 Returns:
468 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700469 """
Stephen Warren10e50632016-01-15 11:15:24 -0700470
471 self._note("error", msg)
472
473 def warning(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700474 """Write an warning note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700475
476 Args:
477 msg: A message describing the warning.
478
479 Returns:
480 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700481 """
Stephen Warren10e50632016-01-15 11:15:24 -0700482
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700483 self.seen_warning = True
Stephen Warren10e50632016-01-15 11:15:24 -0700484 self._note("warning", msg)
485
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700486 def get_and_reset_warning(self):
487 """Get and reset the log warning flag.
488
489 Args:
490 None
491
492 Returns:
493 Whether a warning was seen since the last call.
494 """
495
496 ret = self.seen_warning
497 self.seen_warning = False
498 return ret
499
Stephen Warren10e50632016-01-15 11:15:24 -0700500 def info(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700501 """Write an informational note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700502
503 Args:
504 msg: An informational message.
505
506 Returns:
507 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700508 """
Stephen Warren10e50632016-01-15 11:15:24 -0700509
510 self._note("info", msg)
511
512 def action(self, msg):
Stephen Warren75e731e2016-01-26 13:41:30 -0700513 """Write an action note to the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700514
515 Args:
516 msg: A message describing the action that is being logged.
517
518 Returns:
519 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700520 """
Stephen Warren10e50632016-01-15 11:15:24 -0700521
522 self._note("action", msg)
523
Stephen Warrenb1c556a2017-10-27 11:04:08 -0600524 def _get_time(self):
525 return datetime.datetime.now()
526
527 def timestamp(self):
528 """Write a timestamp to the log file.
529
530 Args:
531 None
532
533 Returns:
534 Nothing.
535 """
536
537 timestamp_now = self._get_time()
538 delta_prev = timestamp_now - self.timestamp_prev
539 delta_start = timestamp_now - self.timestamp_start
540 self.timestamp_prev = timestamp_now
541
542 self._note("timestamp",
543 "TIME: NOW: " + timestamp_now.strftime("%Y/%m/%d %H:%M:%S.%f"))
544 self._note("timestamp",
545 "TIME: SINCE-PREV: " + str(delta_prev))
546 self._note("timestamp",
547 "TIME: SINCE-START: " + str(delta_start))
548
Stephen Warrene3f2a502016-02-03 16:46:34 -0700549 def status_pass(self, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700550 """Write a note to the log file describing test(s) which passed.
Stephen Warren10e50632016-01-15 11:15:24 -0700551
552 Args:
Stephen Warren25b05242016-01-27 23:57:51 -0700553 msg: A message describing the passed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700554 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700555
556 Returns:
557 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700558 """
Stephen Warren10e50632016-01-15 11:15:24 -0700559
Stephen Warrene3f2a502016-02-03 16:46:34 -0700560 self._note("status-pass", msg, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700561
Stephen Warrene27a6ae2018-02-20 12:51:55 -0700562 def status_warning(self, msg, anchor=None):
563 """Write a note to the log file describing test(s) which passed.
564
565 Args:
566 msg: A message describing the passed test(s).
567 anchor: Optional internal link target.
568
569 Returns:
570 Nothing.
571 """
572
573 self._note("status-warning", msg, anchor)
574
Stephen Warrene3f2a502016-02-03 16:46:34 -0700575 def status_skipped(self, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700576 """Write a note to the log file describing skipped test(s).
Stephen Warren10e50632016-01-15 11:15:24 -0700577
578 Args:
Stephen Warren25b05242016-01-27 23:57:51 -0700579 msg: A message describing the skipped test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700580 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700581
582 Returns:
583 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700584 """
Stephen Warren10e50632016-01-15 11:15:24 -0700585
Stephen Warrene3f2a502016-02-03 16:46:34 -0700586 self._note("status-skipped", msg, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700587
Stephen Warrene3f2a502016-02-03 16:46:34 -0700588 def status_xfail(self, msg, anchor=None):
Stephen Warren25b05242016-01-27 23:57:51 -0700589 """Write a note to the log file describing xfailed test(s).
590
591 Args:
592 msg: A message describing the xfailed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700593 anchor: Optional internal link target.
Stephen Warren25b05242016-01-27 23:57:51 -0700594
595 Returns:
596 Nothing.
597 """
598
Stephen Warrene3f2a502016-02-03 16:46:34 -0700599 self._note("status-xfail", msg, anchor)
Stephen Warren25b05242016-01-27 23:57:51 -0700600
Stephen Warrene3f2a502016-02-03 16:46:34 -0700601 def status_xpass(self, msg, anchor=None):
Stephen Warren25b05242016-01-27 23:57:51 -0700602 """Write a note to the log file describing xpassed test(s).
603
604 Args:
605 msg: A message describing the xpassed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700606 anchor: Optional internal link target.
Stephen Warren25b05242016-01-27 23:57:51 -0700607
608 Returns:
609 Nothing.
610 """
611
Stephen Warrene3f2a502016-02-03 16:46:34 -0700612 self._note("status-xpass", msg, anchor)
Stephen Warren25b05242016-01-27 23:57:51 -0700613
Stephen Warrene3f2a502016-02-03 16:46:34 -0700614 def status_fail(self, msg, anchor=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700615 """Write a note to the log file describing failed test(s).
Stephen Warren10e50632016-01-15 11:15:24 -0700616
617 Args:
Stephen Warren25b05242016-01-27 23:57:51 -0700618 msg: A message describing the failed test(s).
Stephen Warrene3f2a502016-02-03 16:46:34 -0700619 anchor: Optional internal link target.
Stephen Warren10e50632016-01-15 11:15:24 -0700620
621 Returns:
622 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700623 """
Stephen Warren10e50632016-01-15 11:15:24 -0700624
Stephen Warrene3f2a502016-02-03 16:46:34 -0700625 self._note("status-fail", msg, anchor)
Stephen Warren10e50632016-01-15 11:15:24 -0700626
627 def get_stream(self, name, chained_file=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700628 """Create an object to log a single stream's data into the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700629
630 This creates a "file-like" object that can be written to in order to
631 write a single stream's data to the log file. The implementation will
632 handle any required interleaving of data (from multiple streams) in
633 the log, in a way that makes it obvious which stream each bit of data
634 came from.
635
636 Args:
637 name: The name of the stream.
638 chained_file: The file-like object to which all stream data should
639 be logged to in addition to this log. Can be None.
640
641 Returns:
642 A file-like object.
Stephen Warren75e731e2016-01-26 13:41:30 -0700643 """
Stephen Warren10e50632016-01-15 11:15:24 -0700644
645 return LogfileStream(self, name, chained_file)
646
647 def get_runner(self, name, chained_file=None):
Stephen Warren75e731e2016-01-26 13:41:30 -0700648 """Create an object that executes processes and logs their output.
Stephen Warren10e50632016-01-15 11:15:24 -0700649
650 Args:
651 name: The name of this sub-process.
652 chained_file: The file-like object to which all stream data should
653 be logged to in addition to logfile. Can be None.
654
655 Returns:
656 A RunAndLog object.
Stephen Warren75e731e2016-01-26 13:41:30 -0700657 """
Stephen Warren10e50632016-01-15 11:15:24 -0700658
659 return RunAndLog(self, name, chained_file)
660
661 def write(self, stream, data, implicit=False):
Stephen Warren75e731e2016-01-26 13:41:30 -0700662 """Write stream data into the log file.
Stephen Warren10e50632016-01-15 11:15:24 -0700663
664 This function should only be used by instances of LogfileStream or
665 RunAndLog.
666
667 Args:
668 stream: The stream whose data is being logged.
669 data: The data to log.
670 implicit: Boolean indicating whether data actually appeared in the
671 stream, or was implicitly generated. A valid use-case is to
672 repeat a shell prompt at the start of each separate log
673 section, which makes the log sections more readable in
674 isolation.
675
676 Returns:
677 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700678 """
Stephen Warren10e50632016-01-15 11:15:24 -0700679
680 if stream != self.last_stream:
681 self._terminate_stream()
Stephen Warrene3f2a502016-02-03 16:46:34 -0700682 self.f.write('<div class="stream block">\n')
683 self.f.write('<div class="stream-header block-header">Stream: ' +
684 stream.name + '</div>\n')
685 self.f.write('<div class="stream-content block-content">\n')
Stephen Warren3deb8962016-01-26 13:41:31 -0700686 self.f.write('<pre>')
Stephen Warren10e50632016-01-15 11:15:24 -0700687 if implicit:
Stephen Warren3deb8962016-01-26 13:41:31 -0700688 self.f.write('<span class="implicit">')
Stephen Warren10e50632016-01-15 11:15:24 -0700689 self.f.write(self._escape(data))
690 if implicit:
Stephen Warren3deb8962016-01-26 13:41:31 -0700691 self.f.write('</span>')
Stephen Warren10e50632016-01-15 11:15:24 -0700692 self.last_stream = stream
693
694 def flush(self):
Stephen Warren75e731e2016-01-26 13:41:30 -0700695 """Flush the log stream, to ensure correct log interleaving.
Stephen Warren10e50632016-01-15 11:15:24 -0700696
697 Args:
698 None.
699
700 Returns:
701 Nothing.
Stephen Warren75e731e2016-01-26 13:41:30 -0700702 """
Stephen Warren10e50632016-01-15 11:15:24 -0700703
704 self.f.flush()