xref: /openbmc/u-boot/test/py/multiplexed_log.py (revision 83357fd5c2ec11b45607dfe35ba5fe5e44d1eee0)
1d201506cSStephen Warren# Copyright (c) 2015 Stephen Warren
2d201506cSStephen Warren# Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
3d201506cSStephen Warren#
4d201506cSStephen Warren# SPDX-License-Identifier: GPL-2.0
5d201506cSStephen Warren
6d201506cSStephen Warren# Generate an HTML-formatted log file containing multiple streams of data,
7d201506cSStephen Warren# each represented in a well-delineated/-structured fashion.
8d201506cSStephen Warren
9d201506cSStephen Warrenimport cgi
10d201506cSStephen Warrenimport os.path
11d201506cSStephen Warrenimport shutil
12d201506cSStephen Warrenimport subprocess
13d201506cSStephen Warren
14d201506cSStephen Warrenmod_dir = os.path.dirname(os.path.abspath(__file__))
15d201506cSStephen Warren
16d201506cSStephen Warrenclass LogfileStream(object):
17e8debf39SStephen Warren    """A file-like object used to write a single logical stream of data into
18d201506cSStephen Warren    a multiplexed log file. Objects of this type should be created by factory
19e8debf39SStephen Warren    functions in the Logfile class rather than directly."""
20d201506cSStephen Warren
21d201506cSStephen Warren    def __init__(self, logfile, name, chained_file):
22e8debf39SStephen Warren        """Initialize a new object.
23d201506cSStephen Warren
24d201506cSStephen Warren        Args:
25d201506cSStephen Warren            logfile: The Logfile object to log to.
26d201506cSStephen Warren            name: The name of this log stream.
27d201506cSStephen Warren            chained_file: The file-like object to which all stream data should be
28d201506cSStephen Warren            logged to in addition to logfile. Can be None.
29d201506cSStephen Warren
30d201506cSStephen Warren        Returns:
31d201506cSStephen Warren            Nothing.
32e8debf39SStephen Warren        """
33d201506cSStephen Warren
34d201506cSStephen Warren        self.logfile = logfile
35d201506cSStephen Warren        self.name = name
36d201506cSStephen Warren        self.chained_file = chained_file
37d201506cSStephen Warren
38d201506cSStephen Warren    def close(self):
39e8debf39SStephen Warren        """Dummy function so that this class is "file-like".
40d201506cSStephen Warren
41d201506cSStephen Warren        Args:
42d201506cSStephen Warren            None.
43d201506cSStephen Warren
44d201506cSStephen Warren        Returns:
45d201506cSStephen Warren            Nothing.
46e8debf39SStephen Warren        """
47d201506cSStephen Warren
48d201506cSStephen Warren        pass
49d201506cSStephen Warren
50d201506cSStephen Warren    def write(self, data, implicit=False):
51e8debf39SStephen Warren        """Write data to the log stream.
52d201506cSStephen Warren
53d201506cSStephen Warren        Args:
54d201506cSStephen Warren            data: The data to write tot he file.
55d201506cSStephen Warren            implicit: Boolean indicating whether data actually appeared in the
56d201506cSStephen Warren                stream, or was implicitly generated. A valid use-case is to
57d201506cSStephen Warren                repeat a shell prompt at the start of each separate log
58d201506cSStephen Warren                section, which makes the log sections more readable in
59d201506cSStephen Warren                isolation.
60d201506cSStephen Warren
61d201506cSStephen Warren        Returns:
62d201506cSStephen Warren            Nothing.
63e8debf39SStephen Warren        """
64d201506cSStephen Warren
65d201506cSStephen Warren        self.logfile.write(self, data, implicit)
66d201506cSStephen Warren        if self.chained_file:
67d201506cSStephen Warren            self.chained_file.write(data)
68d201506cSStephen Warren
69d201506cSStephen Warren    def flush(self):
70e8debf39SStephen Warren        """Flush the log stream, to ensure correct log interleaving.
71d201506cSStephen Warren
72d201506cSStephen Warren        Args:
73d201506cSStephen Warren            None.
74d201506cSStephen Warren
75d201506cSStephen Warren        Returns:
76d201506cSStephen Warren            Nothing.
77e8debf39SStephen Warren        """
78d201506cSStephen Warren
79d201506cSStephen Warren        self.logfile.flush()
80d201506cSStephen Warren        if self.chained_file:
81d201506cSStephen Warren            self.chained_file.flush()
82d201506cSStephen Warren
83d201506cSStephen Warrenclass RunAndLog(object):
84e8debf39SStephen Warren    """A utility object used to execute sub-processes and log their output to
85d201506cSStephen Warren    a multiplexed log file. Objects of this type should be created by factory
86e8debf39SStephen Warren    functions in the Logfile class rather than directly."""
87d201506cSStephen Warren
88d201506cSStephen Warren    def __init__(self, logfile, name, chained_file):
89e8debf39SStephen Warren        """Initialize a new object.
90d201506cSStephen Warren
91d201506cSStephen Warren        Args:
92d201506cSStephen Warren            logfile: The Logfile object to log to.
93d201506cSStephen Warren            name: The name of this log stream or sub-process.
94d201506cSStephen Warren            chained_file: The file-like object to which all stream data should
95d201506cSStephen Warren                be logged to in addition to logfile. Can be None.
96d201506cSStephen Warren
97d201506cSStephen Warren        Returns:
98d201506cSStephen Warren            Nothing.
99e8debf39SStephen Warren        """
100d201506cSStephen Warren
101d201506cSStephen Warren        self.logfile = logfile
102d201506cSStephen Warren        self.name = name
103d201506cSStephen Warren        self.chained_file = chained_file
104d201506cSStephen Warren
105d201506cSStephen Warren    def close(self):
106e8debf39SStephen Warren        """Clean up any resources managed by this object."""
107d201506cSStephen Warren        pass
108d201506cSStephen Warren
1093f2faf73SStephen Warren    def run(self, cmd, cwd=None, ignore_errors=False):
110e8debf39SStephen Warren        """Run a command as a sub-process, and log the results.
111d201506cSStephen Warren
112d201506cSStephen Warren        Args:
113d201506cSStephen Warren            cmd: The command to execute.
114d201506cSStephen Warren            cwd: The directory to run the command in. Can be None to use the
115d201506cSStephen Warren                current directory.
1163f2faf73SStephen Warren            ignore_errors: Indicate whether to ignore errors. If True, the
1173f2faf73SStephen Warren                function will simply return if the command cannot be executed
1183f2faf73SStephen Warren                or exits with an error code, otherwise an exception will be
1193f2faf73SStephen Warren                raised if such problems occur.
120d201506cSStephen Warren
121d201506cSStephen Warren        Returns:
122d201506cSStephen Warren            Nothing.
123e8debf39SStephen Warren        """
124d201506cSStephen Warren
125a2ec5606SStephen Warren        msg = '+' + ' '.join(cmd) + '\n'
126d201506cSStephen Warren        if self.chained_file:
127d201506cSStephen Warren            self.chained_file.write(msg)
128d201506cSStephen Warren        self.logfile.write(self, msg)
129d201506cSStephen Warren
130d201506cSStephen Warren        try:
131d201506cSStephen Warren            p = subprocess.Popen(cmd, cwd=cwd,
132d201506cSStephen Warren                stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
133d201506cSStephen Warren            (stdout, stderr) = p.communicate()
134d201506cSStephen Warren            output = ''
135d201506cSStephen Warren            if stdout:
136d201506cSStephen Warren                if stderr:
137d201506cSStephen Warren                    output += 'stdout:\n'
138d201506cSStephen Warren                output += stdout
139d201506cSStephen Warren            if stderr:
140d201506cSStephen Warren                if stdout:
141d201506cSStephen Warren                    output += 'stderr:\n'
142d201506cSStephen Warren                output += stderr
143d201506cSStephen Warren            exit_status = p.returncode
144d201506cSStephen Warren            exception = None
145d201506cSStephen Warren        except subprocess.CalledProcessError as cpe:
146d201506cSStephen Warren            output = cpe.output
147d201506cSStephen Warren            exit_status = cpe.returncode
148d201506cSStephen Warren            exception = cpe
149d201506cSStephen Warren        except Exception as e:
150d201506cSStephen Warren            output = ''
151d201506cSStephen Warren            exit_status = 0
152d201506cSStephen Warren            exception = e
153d201506cSStephen Warren        if output and not output.endswith('\n'):
154d201506cSStephen Warren            output += '\n'
1553f2faf73SStephen Warren        if exit_status and not exception and not ignore_errors:
156d201506cSStephen Warren            exception = Exception('Exit code: ' + str(exit_status))
157d201506cSStephen Warren        if exception:
158d201506cSStephen Warren            output += str(exception) + '\n'
159d201506cSStephen Warren        self.logfile.write(self, output)
160d201506cSStephen Warren        if self.chained_file:
161d201506cSStephen Warren            self.chained_file.write(output)
162d201506cSStephen Warren        if exception:
163d201506cSStephen Warren            raise exception
164d201506cSStephen Warren
165d201506cSStephen Warrenclass SectionCtxMgr(object):
166e8debf39SStephen Warren    """A context manager for Python's "with" statement, which allows a certain
167d201506cSStephen Warren    portion of test code to be logged to a separate section of the log file.
168d201506cSStephen Warren    Objects of this type should be created by factory functions in the Logfile
169e8debf39SStephen Warren    class rather than directly."""
170d201506cSStephen Warren
171*83357fd5SStephen Warren    def __init__(self, log, marker, anchor):
172e8debf39SStephen Warren        """Initialize a new object.
173d201506cSStephen Warren
174d201506cSStephen Warren        Args:
175d201506cSStephen Warren            log: The Logfile object to log to.
176d201506cSStephen Warren            marker: The name of the nested log section.
177*83357fd5SStephen Warren            anchor: The anchor value to pass to start_section().
178d201506cSStephen Warren
179d201506cSStephen Warren        Returns:
180d201506cSStephen Warren            Nothing.
181e8debf39SStephen Warren        """
182d201506cSStephen Warren
183d201506cSStephen Warren        self.log = log
184d201506cSStephen Warren        self.marker = marker
185*83357fd5SStephen Warren        self.anchor = anchor
186d201506cSStephen Warren
187d201506cSStephen Warren    def __enter__(self):
188*83357fd5SStephen Warren        self.anchor = self.log.start_section(self.marker, self.anchor)
189d201506cSStephen Warren
190d201506cSStephen Warren    def __exit__(self, extype, value, traceback):
191d201506cSStephen Warren        self.log.end_section(self.marker)
192d201506cSStephen Warren
193d201506cSStephen Warrenclass Logfile(object):
194e8debf39SStephen Warren    """Generates an HTML-formatted log file containing multiple streams of
195e8debf39SStephen Warren    data, each represented in a well-delineated/-structured fashion."""
196d201506cSStephen Warren
197d201506cSStephen Warren    def __init__(self, fn):
198e8debf39SStephen Warren        """Initialize a new object.
199d201506cSStephen Warren
200d201506cSStephen Warren        Args:
201d201506cSStephen Warren            fn: The filename to write to.
202d201506cSStephen Warren
203d201506cSStephen Warren        Returns:
204d201506cSStephen Warren            Nothing.
205e8debf39SStephen Warren        """
206d201506cSStephen Warren
207a2ec5606SStephen Warren        self.f = open(fn, 'wt')
208d201506cSStephen Warren        self.last_stream = None
209d201506cSStephen Warren        self.blocks = []
210d201506cSStephen Warren        self.cur_evt = 1
211*83357fd5SStephen Warren        self.anchor = 0
212*83357fd5SStephen Warren
213a2ec5606SStephen Warren        shutil.copy(mod_dir + '/multiplexed_log.css', os.path.dirname(fn))
214a2ec5606SStephen Warren        self.f.write('''\
215d201506cSStephen Warren<html>
216d201506cSStephen Warren<head>
217d201506cSStephen Warren<link rel="stylesheet" type="text/css" href="multiplexed_log.css">
218*83357fd5SStephen Warren<script src="http://code.jquery.com/jquery.min.js"></script>
219*83357fd5SStephen Warren<script>
220*83357fd5SStephen Warren$(document).ready(function () {
221*83357fd5SStephen Warren    // Copy status report HTML to start of log for easy access
222*83357fd5SStephen Warren    sts = $(".block#status_report")[0].outerHTML;
223*83357fd5SStephen Warren    $("tt").prepend(sts);
224*83357fd5SStephen Warren
225*83357fd5SStephen Warren    // Add expand/contract buttons to all block headers
226*83357fd5SStephen Warren    btns = "<span class=\\\"block-expand hidden\\\">[+] </span>" +
227*83357fd5SStephen Warren        "<span class=\\\"block-contract\\\">[-] </span>";
228*83357fd5SStephen Warren    $(".block-header").prepend(btns);
229*83357fd5SStephen Warren
230*83357fd5SStephen Warren    // Pre-contract all blocks which passed, leaving only problem cases
231*83357fd5SStephen Warren    // expanded, to highlight issues the user should look at.
232*83357fd5SStephen Warren    // Only top-level blocks (sections) should have any status
233*83357fd5SStephen Warren    passed_bcs = $(".block-content:has(.status-pass)");
234*83357fd5SStephen Warren    // Some blocks might have multiple status entries (e.g. the status
235*83357fd5SStephen Warren    // report), so take care not to hide blocks with partial success.
236*83357fd5SStephen Warren    passed_bcs = passed_bcs.not(":has(.status-fail)");
237*83357fd5SStephen Warren    passed_bcs = passed_bcs.not(":has(.status-xfail)");
238*83357fd5SStephen Warren    passed_bcs = passed_bcs.not(":has(.status-xpass)");
239*83357fd5SStephen Warren    passed_bcs = passed_bcs.not(":has(.status-skipped)");
240*83357fd5SStephen Warren    // Hide the passed blocks
241*83357fd5SStephen Warren    passed_bcs.addClass("hidden");
242*83357fd5SStephen Warren    // Flip the expand/contract button hiding for those blocks.
243*83357fd5SStephen Warren    bhs = passed_bcs.parent().children(".block-header")
244*83357fd5SStephen Warren    bhs.children(".block-expand").removeClass("hidden");
245*83357fd5SStephen Warren    bhs.children(".block-contract").addClass("hidden");
246*83357fd5SStephen Warren
247*83357fd5SStephen Warren    // Add click handler to block headers.
248*83357fd5SStephen Warren    // The handler expands/contracts the block.
249*83357fd5SStephen Warren    $(".block-header").on("click", function (e) {
250*83357fd5SStephen Warren        var header = $(this);
251*83357fd5SStephen Warren        var content = header.next(".block-content");
252*83357fd5SStephen Warren        var expanded = !content.hasClass("hidden");
253*83357fd5SStephen Warren        if (expanded) {
254*83357fd5SStephen Warren            content.addClass("hidden");
255*83357fd5SStephen Warren            header.children(".block-expand").first().removeClass("hidden");
256*83357fd5SStephen Warren            header.children(".block-contract").first().addClass("hidden");
257*83357fd5SStephen Warren        } else {
258*83357fd5SStephen Warren            header.children(".block-contract").first().removeClass("hidden");
259*83357fd5SStephen Warren            header.children(".block-expand").first().addClass("hidden");
260*83357fd5SStephen Warren            content.removeClass("hidden");
261*83357fd5SStephen Warren        }
262*83357fd5SStephen Warren    });
263*83357fd5SStephen Warren
264*83357fd5SStephen Warren    // When clicking on a link, expand the target block
265*83357fd5SStephen Warren    $("a").on("click", function (e) {
266*83357fd5SStephen Warren        var block = $($(this).attr("href"));
267*83357fd5SStephen Warren        var header = block.children(".block-header");
268*83357fd5SStephen Warren        var content = block.children(".block-content").first();
269*83357fd5SStephen Warren        header.children(".block-contract").first().removeClass("hidden");
270*83357fd5SStephen Warren        header.children(".block-expand").first().addClass("hidden");
271*83357fd5SStephen Warren        content.removeClass("hidden");
272*83357fd5SStephen Warren    });
273*83357fd5SStephen Warren});
274*83357fd5SStephen Warren</script>
275d201506cSStephen Warren</head>
276d201506cSStephen Warren<body>
277d201506cSStephen Warren<tt>
278a2ec5606SStephen Warren''')
279d201506cSStephen Warren
280d201506cSStephen Warren    def close(self):
281e8debf39SStephen Warren        """Close the log file.
282d201506cSStephen Warren
283d201506cSStephen Warren        After calling this function, no more data may be written to the log.
284d201506cSStephen Warren
285d201506cSStephen Warren        Args:
286d201506cSStephen Warren            None.
287d201506cSStephen Warren
288d201506cSStephen Warren        Returns:
289d201506cSStephen Warren            Nothing.
290e8debf39SStephen Warren        """
291d201506cSStephen Warren
292a2ec5606SStephen Warren        self.f.write('''\
293d201506cSStephen Warren</tt>
294d201506cSStephen Warren</body>
295d201506cSStephen Warren</html>
296a2ec5606SStephen Warren''')
297d201506cSStephen Warren        self.f.close()
298d201506cSStephen Warren
299d201506cSStephen Warren    # The set of characters that should be represented as hexadecimal codes in
300d201506cSStephen Warren    # the log file.
301a2ec5606SStephen Warren    _nonprint = ('%' + ''.join(chr(c) for c in range(0, 32) if c not in (9, 10)) +
302a2ec5606SStephen Warren                 ''.join(chr(c) for c in range(127, 256)))
303d201506cSStephen Warren
304d201506cSStephen Warren    def _escape(self, data):
305e8debf39SStephen Warren        """Render data format suitable for inclusion in an HTML document.
306d201506cSStephen Warren
307d201506cSStephen Warren        This includes HTML-escaping certain characters, and translating
308d201506cSStephen Warren        control characters to a hexadecimal representation.
309d201506cSStephen Warren
310d201506cSStephen Warren        Args:
311d201506cSStephen Warren            data: The raw string data to be escaped.
312d201506cSStephen Warren
313d201506cSStephen Warren        Returns:
314d201506cSStephen Warren            An escaped version of the data.
315e8debf39SStephen Warren        """
316d201506cSStephen Warren
317a2ec5606SStephen Warren        data = data.replace(chr(13), '')
318a2ec5606SStephen Warren        data = ''.join((c in self._nonprint) and ('%%%02x' % ord(c)) or
319d201506cSStephen Warren                       c for c in data)
320d201506cSStephen Warren        data = cgi.escape(data)
321d201506cSStephen Warren        return data
322d201506cSStephen Warren
323d201506cSStephen Warren    def _terminate_stream(self):
324e8debf39SStephen Warren        """Write HTML to the log file to terminate the current stream's data.
325d201506cSStephen Warren
326d201506cSStephen Warren        Args:
327d201506cSStephen Warren            None.
328d201506cSStephen Warren
329d201506cSStephen Warren        Returns:
330d201506cSStephen Warren            Nothing.
331e8debf39SStephen Warren        """
332d201506cSStephen Warren
333d201506cSStephen Warren        self.cur_evt += 1
334d201506cSStephen Warren        if not self.last_stream:
335d201506cSStephen Warren            return
336a2ec5606SStephen Warren        self.f.write('</pre>\n')
337*83357fd5SStephen Warren        self.f.write('<div class="stream-trailer block-trailer">End stream: ' +
338a2ec5606SStephen Warren                     self.last_stream.name + '</div>\n')
339a2ec5606SStephen Warren        self.f.write('</div>\n')
340*83357fd5SStephen Warren        self.f.write('</div>\n')
341d201506cSStephen Warren        self.last_stream = None
342d201506cSStephen Warren
343*83357fd5SStephen Warren    def _note(self, note_type, msg, anchor=None):
344e8debf39SStephen Warren        """Write a note or one-off message to the log file.
345d201506cSStephen Warren
346d201506cSStephen Warren        Args:
347d201506cSStephen Warren            note_type: The type of note. This must be a value supported by the
348d201506cSStephen Warren                accompanying multiplexed_log.css.
349d201506cSStephen Warren            msg: The note/message to log.
350*83357fd5SStephen Warren            anchor: Optional internal link target.
351d201506cSStephen Warren
352d201506cSStephen Warren        Returns:
353d201506cSStephen Warren            Nothing.
354e8debf39SStephen Warren        """
355d201506cSStephen Warren
356d201506cSStephen Warren        self._terminate_stream()
357*83357fd5SStephen Warren        self.f.write('<div class="' + note_type + '">\n')
358*83357fd5SStephen Warren        if anchor:
359*83357fd5SStephen Warren            self.f.write('<a href="#%s">\n' % anchor)
360*83357fd5SStephen Warren        self.f.write('<pre>')
361d201506cSStephen Warren        self.f.write(self._escape(msg))
362*83357fd5SStephen Warren        self.f.write('\n</pre>\n')
363*83357fd5SStephen Warren        if anchor:
364*83357fd5SStephen Warren            self.f.write('</a>\n')
365*83357fd5SStephen Warren        self.f.write('</div>\n')
366d201506cSStephen Warren
367*83357fd5SStephen Warren    def start_section(self, marker, anchor=None):
368e8debf39SStephen Warren        """Begin a new nested section in the log file.
369d201506cSStephen Warren
370d201506cSStephen Warren        Args:
371d201506cSStephen Warren            marker: The name of the section that is starting.
372*83357fd5SStephen Warren            anchor: The value to use for the anchor. If None, a unique value
373*83357fd5SStephen Warren              will be calculated and used
374d201506cSStephen Warren
375d201506cSStephen Warren        Returns:
376*83357fd5SStephen Warren            Name of the HTML anchor emitted before section.
377e8debf39SStephen Warren        """
378d201506cSStephen Warren
379d201506cSStephen Warren        self._terminate_stream()
380d201506cSStephen Warren        self.blocks.append(marker)
381*83357fd5SStephen Warren        if not anchor:
382*83357fd5SStephen Warren            self.anchor += 1
383*83357fd5SStephen Warren            anchor = str(self.anchor)
384a2ec5606SStephen Warren        blk_path = '/'.join(self.blocks)
385*83357fd5SStephen Warren        self.f.write('<div class="section block" id="' + anchor + '">\n')
386*83357fd5SStephen Warren        self.f.write('<div class="section-header block-header">Section: ' +
387*83357fd5SStephen Warren                     blk_path + '</div>\n')
388*83357fd5SStephen Warren        self.f.write('<div class="section-content block-content">\n')
389*83357fd5SStephen Warren
390*83357fd5SStephen Warren        return anchor
391d201506cSStephen Warren
392d201506cSStephen Warren    def end_section(self, marker):
393e8debf39SStephen Warren        """Terminate the current nested section in the log file.
394d201506cSStephen Warren
395d201506cSStephen Warren        This function validates proper nesting of start_section() and
396d201506cSStephen Warren        end_section() calls. If a mismatch is found, an exception is raised.
397d201506cSStephen Warren
398d201506cSStephen Warren        Args:
399d201506cSStephen Warren            marker: The name of the section that is ending.
400d201506cSStephen Warren
401d201506cSStephen Warren        Returns:
402d201506cSStephen Warren            Nothing.
403e8debf39SStephen Warren        """
404d201506cSStephen Warren
405d201506cSStephen Warren        if (not self.blocks) or (marker != self.blocks[-1]):
406a2ec5606SStephen Warren            raise Exception('Block nesting mismatch: "%s" "%s"' %
407a2ec5606SStephen Warren                            (marker, '/'.join(self.blocks)))
408d201506cSStephen Warren        self._terminate_stream()
409a2ec5606SStephen Warren        blk_path = '/'.join(self.blocks)
410*83357fd5SStephen Warren        self.f.write('<div class="section-trailer block-trailer">' +
411*83357fd5SStephen Warren                     'End section: ' + blk_path + '</div>\n')
412*83357fd5SStephen Warren        self.f.write('</div>\n')
413a2ec5606SStephen Warren        self.f.write('</div>\n')
414d201506cSStephen Warren        self.blocks.pop()
415d201506cSStephen Warren
416*83357fd5SStephen Warren    def section(self, marker, anchor=None):
417e8debf39SStephen Warren        """Create a temporary section in the log file.
418d201506cSStephen Warren
419d201506cSStephen Warren        This function creates a context manager for Python's "with" statement,
420d201506cSStephen Warren        which allows a certain portion of test code to be logged to a separate
421d201506cSStephen Warren        section of the log file.
422d201506cSStephen Warren
423d201506cSStephen Warren        Usage:
424d201506cSStephen Warren            with log.section("somename"):
425d201506cSStephen Warren                some test code
426d201506cSStephen Warren
427d201506cSStephen Warren        Args:
428d201506cSStephen Warren            marker: The name of the nested section.
429*83357fd5SStephen Warren            anchor: The anchor value to pass to start_section().
430d201506cSStephen Warren
431d201506cSStephen Warren        Returns:
432d201506cSStephen Warren            A context manager object.
433e8debf39SStephen Warren        """
434d201506cSStephen Warren
435*83357fd5SStephen Warren        return SectionCtxMgr(self, marker, anchor)
436d201506cSStephen Warren
437d201506cSStephen Warren    def error(self, msg):
438e8debf39SStephen Warren        """Write an error note to the log file.
439d201506cSStephen Warren
440d201506cSStephen Warren        Args:
441d201506cSStephen Warren            msg: A message describing the error.
442d201506cSStephen Warren
443d201506cSStephen Warren        Returns:
444d201506cSStephen Warren            Nothing.
445e8debf39SStephen Warren        """
446d201506cSStephen Warren
447d201506cSStephen Warren        self._note("error", msg)
448d201506cSStephen Warren
449d201506cSStephen Warren    def warning(self, msg):
450e8debf39SStephen Warren        """Write an warning note to the log file.
451d201506cSStephen Warren
452d201506cSStephen Warren        Args:
453d201506cSStephen Warren            msg: A message describing the warning.
454d201506cSStephen Warren
455d201506cSStephen Warren        Returns:
456d201506cSStephen Warren            Nothing.
457e8debf39SStephen Warren        """
458d201506cSStephen Warren
459d201506cSStephen Warren        self._note("warning", msg)
460d201506cSStephen Warren
461d201506cSStephen Warren    def info(self, msg):
462e8debf39SStephen Warren        """Write an informational note to the log file.
463d201506cSStephen Warren
464d201506cSStephen Warren        Args:
465d201506cSStephen Warren            msg: An informational message.
466d201506cSStephen Warren
467d201506cSStephen Warren        Returns:
468d201506cSStephen Warren            Nothing.
469e8debf39SStephen Warren        """
470d201506cSStephen Warren
471d201506cSStephen Warren        self._note("info", msg)
472d201506cSStephen Warren
473d201506cSStephen Warren    def action(self, msg):
474e8debf39SStephen Warren        """Write an action note to the log file.
475d201506cSStephen Warren
476d201506cSStephen Warren        Args:
477d201506cSStephen Warren            msg: A message describing the action that is being logged.
478d201506cSStephen Warren
479d201506cSStephen Warren        Returns:
480d201506cSStephen Warren            Nothing.
481e8debf39SStephen Warren        """
482d201506cSStephen Warren
483d201506cSStephen Warren        self._note("action", msg)
484d201506cSStephen Warren
485*83357fd5SStephen Warren    def status_pass(self, msg, anchor=None):
486e8debf39SStephen Warren        """Write a note to the log file describing test(s) which passed.
487d201506cSStephen Warren
488d201506cSStephen Warren        Args:
48978b39cc3SStephen Warren            msg: A message describing the passed test(s).
490*83357fd5SStephen Warren            anchor: Optional internal link target.
491d201506cSStephen Warren
492d201506cSStephen Warren        Returns:
493d201506cSStephen Warren            Nothing.
494e8debf39SStephen Warren        """
495d201506cSStephen Warren
496*83357fd5SStephen Warren        self._note("status-pass", msg, anchor)
497d201506cSStephen Warren
498*83357fd5SStephen Warren    def status_skipped(self, msg, anchor=None):
499e8debf39SStephen Warren        """Write a note to the log file describing skipped test(s).
500d201506cSStephen Warren
501d201506cSStephen Warren        Args:
50278b39cc3SStephen Warren            msg: A message describing the skipped test(s).
503*83357fd5SStephen Warren            anchor: Optional internal link target.
504d201506cSStephen Warren
505d201506cSStephen Warren        Returns:
506d201506cSStephen Warren            Nothing.
507e8debf39SStephen Warren        """
508d201506cSStephen Warren
509*83357fd5SStephen Warren        self._note("status-skipped", msg, anchor)
510d201506cSStephen Warren
511*83357fd5SStephen Warren    def status_xfail(self, msg, anchor=None):
51278b39cc3SStephen Warren        """Write a note to the log file describing xfailed test(s).
51378b39cc3SStephen Warren
51478b39cc3SStephen Warren        Args:
51578b39cc3SStephen Warren            msg: A message describing the xfailed test(s).
516*83357fd5SStephen Warren            anchor: Optional internal link target.
51778b39cc3SStephen Warren
51878b39cc3SStephen Warren        Returns:
51978b39cc3SStephen Warren            Nothing.
52078b39cc3SStephen Warren        """
52178b39cc3SStephen Warren
522*83357fd5SStephen Warren        self._note("status-xfail", msg, anchor)
52378b39cc3SStephen Warren
524*83357fd5SStephen Warren    def status_xpass(self, msg, anchor=None):
52578b39cc3SStephen Warren        """Write a note to the log file describing xpassed test(s).
52678b39cc3SStephen Warren
52778b39cc3SStephen Warren        Args:
52878b39cc3SStephen Warren            msg: A message describing the xpassed test(s).
529*83357fd5SStephen Warren            anchor: Optional internal link target.
53078b39cc3SStephen Warren
53178b39cc3SStephen Warren        Returns:
53278b39cc3SStephen Warren            Nothing.
53378b39cc3SStephen Warren        """
53478b39cc3SStephen Warren
535*83357fd5SStephen Warren        self._note("status-xpass", msg, anchor)
53678b39cc3SStephen Warren
537*83357fd5SStephen Warren    def status_fail(self, msg, anchor=None):
538e8debf39SStephen Warren        """Write a note to the log file describing failed test(s).
539d201506cSStephen Warren
540d201506cSStephen Warren        Args:
54178b39cc3SStephen Warren            msg: A message describing the failed test(s).
542*83357fd5SStephen Warren            anchor: Optional internal link target.
543d201506cSStephen Warren
544d201506cSStephen Warren        Returns:
545d201506cSStephen Warren            Nothing.
546e8debf39SStephen Warren        """
547d201506cSStephen Warren
548*83357fd5SStephen Warren        self._note("status-fail", msg, anchor)
549d201506cSStephen Warren
550d201506cSStephen Warren    def get_stream(self, name, chained_file=None):
551e8debf39SStephen Warren        """Create an object to log a single stream's data into the log file.
552d201506cSStephen Warren
553d201506cSStephen Warren        This creates a "file-like" object that can be written to in order to
554d201506cSStephen Warren        write a single stream's data to the log file. The implementation will
555d201506cSStephen Warren        handle any required interleaving of data (from multiple streams) in
556d201506cSStephen Warren        the log, in a way that makes it obvious which stream each bit of data
557d201506cSStephen Warren        came from.
558d201506cSStephen Warren
559d201506cSStephen Warren        Args:
560d201506cSStephen Warren            name: The name of the stream.
561d201506cSStephen Warren            chained_file: The file-like object to which all stream data should
562d201506cSStephen Warren                be logged to in addition to this log. Can be None.
563d201506cSStephen Warren
564d201506cSStephen Warren        Returns:
565d201506cSStephen Warren            A file-like object.
566e8debf39SStephen Warren        """
567d201506cSStephen Warren
568d201506cSStephen Warren        return LogfileStream(self, name, chained_file)
569d201506cSStephen Warren
570d201506cSStephen Warren    def get_runner(self, name, chained_file=None):
571e8debf39SStephen Warren        """Create an object that executes processes and logs their output.
572d201506cSStephen Warren
573d201506cSStephen Warren        Args:
574d201506cSStephen Warren            name: The name of this sub-process.
575d201506cSStephen Warren            chained_file: The file-like object to which all stream data should
576d201506cSStephen Warren                be logged to in addition to logfile. Can be None.
577d201506cSStephen Warren
578d201506cSStephen Warren        Returns:
579d201506cSStephen Warren            A RunAndLog object.
580e8debf39SStephen Warren        """
581d201506cSStephen Warren
582d201506cSStephen Warren        return RunAndLog(self, name, chained_file)
583d201506cSStephen Warren
584d201506cSStephen Warren    def write(self, stream, data, implicit=False):
585e8debf39SStephen Warren        """Write stream data into the log file.
586d201506cSStephen Warren
587d201506cSStephen Warren        This function should only be used by instances of LogfileStream or
588d201506cSStephen Warren        RunAndLog.
589d201506cSStephen Warren
590d201506cSStephen Warren        Args:
591d201506cSStephen Warren            stream: The stream whose data is being logged.
592d201506cSStephen Warren            data: The data to log.
593d201506cSStephen Warren            implicit: Boolean indicating whether data actually appeared in the
594d201506cSStephen Warren                stream, or was implicitly generated. A valid use-case is to
595d201506cSStephen Warren                repeat a shell prompt at the start of each separate log
596d201506cSStephen Warren                section, which makes the log sections more readable in
597d201506cSStephen Warren                isolation.
598d201506cSStephen Warren
599d201506cSStephen Warren        Returns:
600d201506cSStephen Warren            Nothing.
601e8debf39SStephen Warren        """
602d201506cSStephen Warren
603d201506cSStephen Warren        if stream != self.last_stream:
604d201506cSStephen Warren            self._terminate_stream()
605*83357fd5SStephen Warren            self.f.write('<div class="stream block">\n')
606*83357fd5SStephen Warren            self.f.write('<div class="stream-header block-header">Stream: ' +
607*83357fd5SStephen Warren                         stream.name + '</div>\n')
608*83357fd5SStephen Warren            self.f.write('<div class="stream-content block-content">\n')
609a2ec5606SStephen Warren            self.f.write('<pre>')
610d201506cSStephen Warren        if implicit:
611a2ec5606SStephen Warren            self.f.write('<span class="implicit">')
612d201506cSStephen Warren        self.f.write(self._escape(data))
613d201506cSStephen Warren        if implicit:
614a2ec5606SStephen Warren            self.f.write('</span>')
615d201506cSStephen Warren        self.last_stream = stream
616d201506cSStephen Warren
617d201506cSStephen Warren    def flush(self):
618e8debf39SStephen Warren        """Flush the log stream, to ensure correct log interleaving.
619d201506cSStephen Warren
620d201506cSStephen Warren        Args:
621d201506cSStephen Warren            None.
622d201506cSStephen Warren
623d201506cSStephen Warren        Returns:
624d201506cSStephen Warren            Nothing.
625e8debf39SStephen Warren        """
626d201506cSStephen Warren
627d201506cSStephen Warren        self.f.flush()
628