xref: /openbmc/u-boot/test/py/multiplexed_log.py (revision 117eeb7f84176268c086db7764778d99d832cd71)
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
10486845bf3SSimon Glass        self.output = None
1057f64b187SSimon Glass        self.exit_status = None
106d201506cSStephen Warren
107d201506cSStephen Warren    def close(self):
108e8debf39SStephen Warren        """Clean up any resources managed by this object."""
109d201506cSStephen Warren        pass
110d201506cSStephen Warren
1113f2faf73SStephen Warren    def run(self, cmd, cwd=None, ignore_errors=False):
112e8debf39SStephen Warren        """Run a command as a sub-process, and log the results.
113d201506cSStephen Warren
11486845bf3SSimon Glass        The output is available at self.output which can be useful if there is
11586845bf3SSimon Glass        an exception.
11686845bf3SSimon Glass
117d201506cSStephen Warren        Args:
118d201506cSStephen Warren            cmd: The command to execute.
119d201506cSStephen Warren            cwd: The directory to run the command in. Can be None to use the
120d201506cSStephen Warren                current directory.
1213f2faf73SStephen Warren            ignore_errors: Indicate whether to ignore errors. If True, the
1223f2faf73SStephen Warren                function will simply return if the command cannot be executed
1233f2faf73SStephen Warren                or exits with an error code, otherwise an exception will be
1243f2faf73SStephen Warren                raised if such problems occur.
125d201506cSStephen Warren
126d201506cSStephen Warren        Returns:
1273b8d9d97SSimon Glass            The output as a string.
128e8debf39SStephen Warren        """
129d201506cSStephen Warren
130a2ec5606SStephen Warren        msg = '+' + ' '.join(cmd) + '\n'
131d201506cSStephen Warren        if self.chained_file:
132d201506cSStephen Warren            self.chained_file.write(msg)
133d201506cSStephen Warren        self.logfile.write(self, msg)
134d201506cSStephen Warren
135d201506cSStephen Warren        try:
136d201506cSStephen Warren            p = subprocess.Popen(cmd, cwd=cwd,
137d201506cSStephen Warren                stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
138d201506cSStephen Warren            (stdout, stderr) = p.communicate()
139d201506cSStephen Warren            output = ''
140d201506cSStephen Warren            if stdout:
141d201506cSStephen Warren                if stderr:
142d201506cSStephen Warren                    output += 'stdout:\n'
143d201506cSStephen Warren                output += stdout
144d201506cSStephen Warren            if stderr:
145d201506cSStephen Warren                if stdout:
146d201506cSStephen Warren                    output += 'stderr:\n'
147d201506cSStephen Warren                output += stderr
148d201506cSStephen Warren            exit_status = p.returncode
149d201506cSStephen Warren            exception = None
150d201506cSStephen Warren        except subprocess.CalledProcessError as cpe:
151d201506cSStephen Warren            output = cpe.output
152d201506cSStephen Warren            exit_status = cpe.returncode
153d201506cSStephen Warren            exception = cpe
154d201506cSStephen Warren        except Exception as e:
155d201506cSStephen Warren            output = ''
156d201506cSStephen Warren            exit_status = 0
157d201506cSStephen Warren            exception = e
158d201506cSStephen Warren        if output and not output.endswith('\n'):
159d201506cSStephen Warren            output += '\n'
1603f2faf73SStephen Warren        if exit_status and not exception and not ignore_errors:
161d201506cSStephen Warren            exception = Exception('Exit code: ' + str(exit_status))
162d201506cSStephen Warren        if exception:
163d201506cSStephen Warren            output += str(exception) + '\n'
164d201506cSStephen Warren        self.logfile.write(self, output)
165d201506cSStephen Warren        if self.chained_file:
166d201506cSStephen Warren            self.chained_file.write(output)
16786845bf3SSimon Glass
16886845bf3SSimon Glass        # Store the output so it can be accessed if we raise an exception.
16986845bf3SSimon Glass        self.output = output
1707f64b187SSimon Glass        self.exit_status = exit_status
171d201506cSStephen Warren        if exception:
172d201506cSStephen Warren            raise exception
1733b8d9d97SSimon Glass        return output
174d201506cSStephen Warren
175d201506cSStephen Warrenclass SectionCtxMgr(object):
176e8debf39SStephen Warren    """A context manager for Python's "with" statement, which allows a certain
177d201506cSStephen Warren    portion of test code to be logged to a separate section of the log file.
178d201506cSStephen Warren    Objects of this type should be created by factory functions in the Logfile
179e8debf39SStephen Warren    class rather than directly."""
180d201506cSStephen Warren
18183357fd5SStephen Warren    def __init__(self, log, marker, anchor):
182e8debf39SStephen Warren        """Initialize a new object.
183d201506cSStephen Warren
184d201506cSStephen Warren        Args:
185d201506cSStephen Warren            log: The Logfile object to log to.
186d201506cSStephen Warren            marker: The name of the nested log section.
18783357fd5SStephen Warren            anchor: The anchor value to pass to start_section().
188d201506cSStephen Warren
189d201506cSStephen Warren        Returns:
190d201506cSStephen Warren            Nothing.
191e8debf39SStephen Warren        """
192d201506cSStephen Warren
193d201506cSStephen Warren        self.log = log
194d201506cSStephen Warren        self.marker = marker
19583357fd5SStephen Warren        self.anchor = anchor
196d201506cSStephen Warren
197d201506cSStephen Warren    def __enter__(self):
19883357fd5SStephen Warren        self.anchor = self.log.start_section(self.marker, self.anchor)
199d201506cSStephen Warren
200d201506cSStephen Warren    def __exit__(self, extype, value, traceback):
201d201506cSStephen Warren        self.log.end_section(self.marker)
202d201506cSStephen Warren
203d201506cSStephen Warrenclass Logfile(object):
204e8debf39SStephen Warren    """Generates an HTML-formatted log file containing multiple streams of
205e8debf39SStephen Warren    data, each represented in a well-delineated/-structured fashion."""
206d201506cSStephen Warren
207d201506cSStephen Warren    def __init__(self, fn):
208e8debf39SStephen Warren        """Initialize a new object.
209d201506cSStephen Warren
210d201506cSStephen Warren        Args:
211d201506cSStephen Warren            fn: The filename to write to.
212d201506cSStephen Warren
213d201506cSStephen Warren        Returns:
214d201506cSStephen Warren            Nothing.
215e8debf39SStephen Warren        """
216d201506cSStephen Warren
217a2ec5606SStephen Warren        self.f = open(fn, 'wt')
218d201506cSStephen Warren        self.last_stream = None
219d201506cSStephen Warren        self.blocks = []
220d201506cSStephen Warren        self.cur_evt = 1
22183357fd5SStephen Warren        self.anchor = 0
22283357fd5SStephen Warren
223a2ec5606SStephen Warren        shutil.copy(mod_dir + '/multiplexed_log.css', os.path.dirname(fn))
224a2ec5606SStephen Warren        self.f.write('''\
225d201506cSStephen Warren<html>
226d201506cSStephen Warren<head>
227d201506cSStephen Warren<link rel="stylesheet" type="text/css" href="multiplexed_log.css">
22883357fd5SStephen Warren<script src="http://code.jquery.com/jquery.min.js"></script>
22983357fd5SStephen Warren<script>
23083357fd5SStephen Warren$(document).ready(function () {
23183357fd5SStephen Warren    // Copy status report HTML to start of log for easy access
23283357fd5SStephen Warren    sts = $(".block#status_report")[0].outerHTML;
23383357fd5SStephen Warren    $("tt").prepend(sts);
23483357fd5SStephen Warren
23583357fd5SStephen Warren    // Add expand/contract buttons to all block headers
23683357fd5SStephen Warren    btns = "<span class=\\\"block-expand hidden\\\">[+] </span>" +
23783357fd5SStephen Warren        "<span class=\\\"block-contract\\\">[-] </span>";
23883357fd5SStephen Warren    $(".block-header").prepend(btns);
23983357fd5SStephen Warren
24083357fd5SStephen Warren    // Pre-contract all blocks which passed, leaving only problem cases
24183357fd5SStephen Warren    // expanded, to highlight issues the user should look at.
24283357fd5SStephen Warren    // Only top-level blocks (sections) should have any status
24383357fd5SStephen Warren    passed_bcs = $(".block-content:has(.status-pass)");
24483357fd5SStephen Warren    // Some blocks might have multiple status entries (e.g. the status
24583357fd5SStephen Warren    // report), so take care not to hide blocks with partial success.
24683357fd5SStephen Warren    passed_bcs = passed_bcs.not(":has(.status-fail)");
24783357fd5SStephen Warren    passed_bcs = passed_bcs.not(":has(.status-xfail)");
24883357fd5SStephen Warren    passed_bcs = passed_bcs.not(":has(.status-xpass)");
24983357fd5SStephen Warren    passed_bcs = passed_bcs.not(":has(.status-skipped)");
25083357fd5SStephen Warren    // Hide the passed blocks
25183357fd5SStephen Warren    passed_bcs.addClass("hidden");
25283357fd5SStephen Warren    // Flip the expand/contract button hiding for those blocks.
25383357fd5SStephen Warren    bhs = passed_bcs.parent().children(".block-header")
25483357fd5SStephen Warren    bhs.children(".block-expand").removeClass("hidden");
25583357fd5SStephen Warren    bhs.children(".block-contract").addClass("hidden");
25683357fd5SStephen Warren
25783357fd5SStephen Warren    // Add click handler to block headers.
25883357fd5SStephen Warren    // The handler expands/contracts the block.
25983357fd5SStephen Warren    $(".block-header").on("click", function (e) {
26083357fd5SStephen Warren        var header = $(this);
26183357fd5SStephen Warren        var content = header.next(".block-content");
26283357fd5SStephen Warren        var expanded = !content.hasClass("hidden");
26383357fd5SStephen Warren        if (expanded) {
26483357fd5SStephen Warren            content.addClass("hidden");
26583357fd5SStephen Warren            header.children(".block-expand").first().removeClass("hidden");
26683357fd5SStephen Warren            header.children(".block-contract").first().addClass("hidden");
26783357fd5SStephen Warren        } else {
26883357fd5SStephen Warren            header.children(".block-contract").first().removeClass("hidden");
26983357fd5SStephen Warren            header.children(".block-expand").first().addClass("hidden");
27083357fd5SStephen Warren            content.removeClass("hidden");
27183357fd5SStephen Warren        }
27283357fd5SStephen Warren    });
27383357fd5SStephen Warren
27483357fd5SStephen Warren    // When clicking on a link, expand the target block
27583357fd5SStephen Warren    $("a").on("click", function (e) {
27683357fd5SStephen Warren        var block = $($(this).attr("href"));
27783357fd5SStephen Warren        var header = block.children(".block-header");
27883357fd5SStephen Warren        var content = block.children(".block-content").first();
27983357fd5SStephen Warren        header.children(".block-contract").first().removeClass("hidden");
28083357fd5SStephen Warren        header.children(".block-expand").first().addClass("hidden");
28183357fd5SStephen Warren        content.removeClass("hidden");
28283357fd5SStephen Warren    });
28383357fd5SStephen Warren});
28483357fd5SStephen Warren</script>
285d201506cSStephen Warren</head>
286d201506cSStephen Warren<body>
287d201506cSStephen Warren<tt>
288a2ec5606SStephen Warren''')
289d201506cSStephen Warren
290d201506cSStephen Warren    def close(self):
291e8debf39SStephen Warren        """Close the log file.
292d201506cSStephen Warren
293d201506cSStephen Warren        After calling this function, no more data may be written to the log.
294d201506cSStephen Warren
295d201506cSStephen Warren        Args:
296d201506cSStephen Warren            None.
297d201506cSStephen Warren
298d201506cSStephen Warren        Returns:
299d201506cSStephen Warren            Nothing.
300e8debf39SStephen Warren        """
301d201506cSStephen Warren
302a2ec5606SStephen Warren        self.f.write('''\
303d201506cSStephen Warren</tt>
304d201506cSStephen Warren</body>
305d201506cSStephen Warren</html>
306a2ec5606SStephen Warren''')
307d201506cSStephen Warren        self.f.close()
308d201506cSStephen Warren
309d201506cSStephen Warren    # The set of characters that should be represented as hexadecimal codes in
310d201506cSStephen Warren    # the log file.
311a2ec5606SStephen Warren    _nonprint = ('%' + ''.join(chr(c) for c in range(0, 32) if c not in (9, 10)) +
312a2ec5606SStephen Warren                 ''.join(chr(c) for c in range(127, 256)))
313d201506cSStephen Warren
314d201506cSStephen Warren    def _escape(self, data):
315e8debf39SStephen Warren        """Render data format suitable for inclusion in an HTML document.
316d201506cSStephen Warren
317d201506cSStephen Warren        This includes HTML-escaping certain characters, and translating
318d201506cSStephen Warren        control characters to a hexadecimal representation.
319d201506cSStephen Warren
320d201506cSStephen Warren        Args:
321d201506cSStephen Warren            data: The raw string data to be escaped.
322d201506cSStephen Warren
323d201506cSStephen Warren        Returns:
324d201506cSStephen Warren            An escaped version of the data.
325e8debf39SStephen Warren        """
326d201506cSStephen Warren
327a2ec5606SStephen Warren        data = data.replace(chr(13), '')
328a2ec5606SStephen Warren        data = ''.join((c in self._nonprint) and ('%%%02x' % ord(c)) or
329d201506cSStephen Warren                       c for c in data)
330d201506cSStephen Warren        data = cgi.escape(data)
331d201506cSStephen Warren        return data
332d201506cSStephen Warren
333d201506cSStephen Warren    def _terminate_stream(self):
334e8debf39SStephen Warren        """Write HTML to the log file to terminate the current stream's data.
335d201506cSStephen Warren
336d201506cSStephen Warren        Args:
337d201506cSStephen Warren            None.
338d201506cSStephen Warren
339d201506cSStephen Warren        Returns:
340d201506cSStephen Warren            Nothing.
341e8debf39SStephen Warren        """
342d201506cSStephen Warren
343d201506cSStephen Warren        self.cur_evt += 1
344d201506cSStephen Warren        if not self.last_stream:
345d201506cSStephen Warren            return
346a2ec5606SStephen Warren        self.f.write('</pre>\n')
34783357fd5SStephen Warren        self.f.write('<div class="stream-trailer block-trailer">End stream: ' +
348a2ec5606SStephen Warren                     self.last_stream.name + '</div>\n')
349a2ec5606SStephen Warren        self.f.write('</div>\n')
35083357fd5SStephen Warren        self.f.write('</div>\n')
351d201506cSStephen Warren        self.last_stream = None
352d201506cSStephen Warren
35383357fd5SStephen Warren    def _note(self, note_type, msg, anchor=None):
354e8debf39SStephen Warren        """Write a note or one-off message to the log file.
355d201506cSStephen Warren
356d201506cSStephen Warren        Args:
357d201506cSStephen Warren            note_type: The type of note. This must be a value supported by the
358d201506cSStephen Warren                accompanying multiplexed_log.css.
359d201506cSStephen Warren            msg: The note/message to log.
36083357fd5SStephen Warren            anchor: Optional internal link target.
361d201506cSStephen Warren
362d201506cSStephen Warren        Returns:
363d201506cSStephen Warren            Nothing.
364e8debf39SStephen Warren        """
365d201506cSStephen Warren
366d201506cSStephen Warren        self._terminate_stream()
36783357fd5SStephen Warren        self.f.write('<div class="' + note_type + '">\n')
36883357fd5SStephen Warren        self.f.write('<pre>')
36983357fd5SStephen Warren        if anchor:
370*117eeb7fSStephen Warren            self.f.write('<a href="#%s">' % anchor)
371*117eeb7fSStephen Warren        self.f.write(self._escape(msg))
372*117eeb7fSStephen Warren        if anchor:
373*117eeb7fSStephen Warren            self.f.write('</a>')
374*117eeb7fSStephen Warren        self.f.write('\n</pre>\n')
37583357fd5SStephen Warren        self.f.write('</div>\n')
376d201506cSStephen Warren
37783357fd5SStephen Warren    def start_section(self, marker, anchor=None):
378e8debf39SStephen Warren        """Begin a new nested section in the log file.
379d201506cSStephen Warren
380d201506cSStephen Warren        Args:
381d201506cSStephen Warren            marker: The name of the section that is starting.
38283357fd5SStephen Warren            anchor: The value to use for the anchor. If None, a unique value
38383357fd5SStephen Warren              will be calculated and used
384d201506cSStephen Warren
385d201506cSStephen Warren        Returns:
38683357fd5SStephen Warren            Name of the HTML anchor emitted before section.
387e8debf39SStephen Warren        """
388d201506cSStephen Warren
389d201506cSStephen Warren        self._terminate_stream()
390d201506cSStephen Warren        self.blocks.append(marker)
39183357fd5SStephen Warren        if not anchor:
39283357fd5SStephen Warren            self.anchor += 1
39383357fd5SStephen Warren            anchor = str(self.anchor)
394a2ec5606SStephen Warren        blk_path = '/'.join(self.blocks)
39583357fd5SStephen Warren        self.f.write('<div class="section block" id="' + anchor + '">\n')
39683357fd5SStephen Warren        self.f.write('<div class="section-header block-header">Section: ' +
39783357fd5SStephen Warren                     blk_path + '</div>\n')
39883357fd5SStephen Warren        self.f.write('<div class="section-content block-content">\n')
39983357fd5SStephen Warren
40083357fd5SStephen Warren        return anchor
401d201506cSStephen Warren
402d201506cSStephen Warren    def end_section(self, marker):
403e8debf39SStephen Warren        """Terminate the current nested section in the log file.
404d201506cSStephen Warren
405d201506cSStephen Warren        This function validates proper nesting of start_section() and
406d201506cSStephen Warren        end_section() calls. If a mismatch is found, an exception is raised.
407d201506cSStephen Warren
408d201506cSStephen Warren        Args:
409d201506cSStephen Warren            marker: The name of the section that is ending.
410d201506cSStephen Warren
411d201506cSStephen Warren        Returns:
412d201506cSStephen Warren            Nothing.
413e8debf39SStephen Warren        """
414d201506cSStephen Warren
415d201506cSStephen Warren        if (not self.blocks) or (marker != self.blocks[-1]):
416a2ec5606SStephen Warren            raise Exception('Block nesting mismatch: "%s" "%s"' %
417a2ec5606SStephen Warren                            (marker, '/'.join(self.blocks)))
418d201506cSStephen Warren        self._terminate_stream()
419a2ec5606SStephen Warren        blk_path = '/'.join(self.blocks)
42083357fd5SStephen Warren        self.f.write('<div class="section-trailer block-trailer">' +
42183357fd5SStephen Warren                     'End section: ' + blk_path + '</div>\n')
42283357fd5SStephen Warren        self.f.write('</div>\n')
423a2ec5606SStephen Warren        self.f.write('</div>\n')
424d201506cSStephen Warren        self.blocks.pop()
425d201506cSStephen Warren
42683357fd5SStephen Warren    def section(self, marker, anchor=None):
427e8debf39SStephen Warren        """Create a temporary section in the log file.
428d201506cSStephen Warren
429d201506cSStephen Warren        This function creates a context manager for Python's "with" statement,
430d201506cSStephen Warren        which allows a certain portion of test code to be logged to a separate
431d201506cSStephen Warren        section of the log file.
432d201506cSStephen Warren
433d201506cSStephen Warren        Usage:
434d201506cSStephen Warren            with log.section("somename"):
435d201506cSStephen Warren                some test code
436d201506cSStephen Warren
437d201506cSStephen Warren        Args:
438d201506cSStephen Warren            marker: The name of the nested section.
43983357fd5SStephen Warren            anchor: The anchor value to pass to start_section().
440d201506cSStephen Warren
441d201506cSStephen Warren        Returns:
442d201506cSStephen Warren            A context manager object.
443e8debf39SStephen Warren        """
444d201506cSStephen Warren
44583357fd5SStephen Warren        return SectionCtxMgr(self, marker, anchor)
446d201506cSStephen Warren
447d201506cSStephen Warren    def error(self, msg):
448e8debf39SStephen Warren        """Write an error note to the log file.
449d201506cSStephen Warren
450d201506cSStephen Warren        Args:
451d201506cSStephen Warren            msg: A message describing the error.
452d201506cSStephen Warren
453d201506cSStephen Warren        Returns:
454d201506cSStephen Warren            Nothing.
455e8debf39SStephen Warren        """
456d201506cSStephen Warren
457d201506cSStephen Warren        self._note("error", msg)
458d201506cSStephen Warren
459d201506cSStephen Warren    def warning(self, msg):
460e8debf39SStephen Warren        """Write an warning note to the log file.
461d201506cSStephen Warren
462d201506cSStephen Warren        Args:
463d201506cSStephen Warren            msg: A message describing the warning.
464d201506cSStephen Warren
465d201506cSStephen Warren        Returns:
466d201506cSStephen Warren            Nothing.
467e8debf39SStephen Warren        """
468d201506cSStephen Warren
469d201506cSStephen Warren        self._note("warning", msg)
470d201506cSStephen Warren
471d201506cSStephen Warren    def info(self, msg):
472e8debf39SStephen Warren        """Write an informational note to the log file.
473d201506cSStephen Warren
474d201506cSStephen Warren        Args:
475d201506cSStephen Warren            msg: An informational message.
476d201506cSStephen Warren
477d201506cSStephen Warren        Returns:
478d201506cSStephen Warren            Nothing.
479e8debf39SStephen Warren        """
480d201506cSStephen Warren
481d201506cSStephen Warren        self._note("info", msg)
482d201506cSStephen Warren
483d201506cSStephen Warren    def action(self, msg):
484e8debf39SStephen Warren        """Write an action note to the log file.
485d201506cSStephen Warren
486d201506cSStephen Warren        Args:
487d201506cSStephen Warren            msg: A message describing the action that is being logged.
488d201506cSStephen Warren
489d201506cSStephen Warren        Returns:
490d201506cSStephen Warren            Nothing.
491e8debf39SStephen Warren        """
492d201506cSStephen Warren
493d201506cSStephen Warren        self._note("action", msg)
494d201506cSStephen Warren
49583357fd5SStephen Warren    def status_pass(self, msg, anchor=None):
496e8debf39SStephen Warren        """Write a note to the log file describing test(s) which passed.
497d201506cSStephen Warren
498d201506cSStephen Warren        Args:
49978b39cc3SStephen Warren            msg: A message describing the passed test(s).
50083357fd5SStephen Warren            anchor: Optional internal link target.
501d201506cSStephen Warren
502d201506cSStephen Warren        Returns:
503d201506cSStephen Warren            Nothing.
504e8debf39SStephen Warren        """
505d201506cSStephen Warren
50683357fd5SStephen Warren        self._note("status-pass", msg, anchor)
507d201506cSStephen Warren
50883357fd5SStephen Warren    def status_skipped(self, msg, anchor=None):
509e8debf39SStephen Warren        """Write a note to the log file describing skipped test(s).
510d201506cSStephen Warren
511d201506cSStephen Warren        Args:
51278b39cc3SStephen Warren            msg: A message describing the skipped test(s).
51383357fd5SStephen Warren            anchor: Optional internal link target.
514d201506cSStephen Warren
515d201506cSStephen Warren        Returns:
516d201506cSStephen Warren            Nothing.
517e8debf39SStephen Warren        """
518d201506cSStephen Warren
51983357fd5SStephen Warren        self._note("status-skipped", msg, anchor)
520d201506cSStephen Warren
52183357fd5SStephen Warren    def status_xfail(self, msg, anchor=None):
52278b39cc3SStephen Warren        """Write a note to the log file describing xfailed test(s).
52378b39cc3SStephen Warren
52478b39cc3SStephen Warren        Args:
52578b39cc3SStephen Warren            msg: A message describing the xfailed test(s).
52683357fd5SStephen Warren            anchor: Optional internal link target.
52778b39cc3SStephen Warren
52878b39cc3SStephen Warren        Returns:
52978b39cc3SStephen Warren            Nothing.
53078b39cc3SStephen Warren        """
53178b39cc3SStephen Warren
53283357fd5SStephen Warren        self._note("status-xfail", msg, anchor)
53378b39cc3SStephen Warren
53483357fd5SStephen Warren    def status_xpass(self, msg, anchor=None):
53578b39cc3SStephen Warren        """Write a note to the log file describing xpassed test(s).
53678b39cc3SStephen Warren
53778b39cc3SStephen Warren        Args:
53878b39cc3SStephen Warren            msg: A message describing the xpassed test(s).
53983357fd5SStephen Warren            anchor: Optional internal link target.
54078b39cc3SStephen Warren
54178b39cc3SStephen Warren        Returns:
54278b39cc3SStephen Warren            Nothing.
54378b39cc3SStephen Warren        """
54478b39cc3SStephen Warren
54583357fd5SStephen Warren        self._note("status-xpass", msg, anchor)
54678b39cc3SStephen Warren
54783357fd5SStephen Warren    def status_fail(self, msg, anchor=None):
548e8debf39SStephen Warren        """Write a note to the log file describing failed test(s).
549d201506cSStephen Warren
550d201506cSStephen Warren        Args:
55178b39cc3SStephen Warren            msg: A message describing the failed test(s).
55283357fd5SStephen Warren            anchor: Optional internal link target.
553d201506cSStephen Warren
554d201506cSStephen Warren        Returns:
555d201506cSStephen Warren            Nothing.
556e8debf39SStephen Warren        """
557d201506cSStephen Warren
55883357fd5SStephen Warren        self._note("status-fail", msg, anchor)
559d201506cSStephen Warren
560d201506cSStephen Warren    def get_stream(self, name, chained_file=None):
561e8debf39SStephen Warren        """Create an object to log a single stream's data into the log file.
562d201506cSStephen Warren
563d201506cSStephen Warren        This creates a "file-like" object that can be written to in order to
564d201506cSStephen Warren        write a single stream's data to the log file. The implementation will
565d201506cSStephen Warren        handle any required interleaving of data (from multiple streams) in
566d201506cSStephen Warren        the log, in a way that makes it obvious which stream each bit of data
567d201506cSStephen Warren        came from.
568d201506cSStephen Warren
569d201506cSStephen Warren        Args:
570d201506cSStephen Warren            name: The name of the stream.
571d201506cSStephen Warren            chained_file: The file-like object to which all stream data should
572d201506cSStephen Warren                be logged to in addition to this log. Can be None.
573d201506cSStephen Warren
574d201506cSStephen Warren        Returns:
575d201506cSStephen Warren            A file-like object.
576e8debf39SStephen Warren        """
577d201506cSStephen Warren
578d201506cSStephen Warren        return LogfileStream(self, name, chained_file)
579d201506cSStephen Warren
580d201506cSStephen Warren    def get_runner(self, name, chained_file=None):
581e8debf39SStephen Warren        """Create an object that executes processes and logs their output.
582d201506cSStephen Warren
583d201506cSStephen Warren        Args:
584d201506cSStephen Warren            name: The name of this sub-process.
585d201506cSStephen Warren            chained_file: The file-like object to which all stream data should
586d201506cSStephen Warren                be logged to in addition to logfile. Can be None.
587d201506cSStephen Warren
588d201506cSStephen Warren        Returns:
589d201506cSStephen Warren            A RunAndLog object.
590e8debf39SStephen Warren        """
591d201506cSStephen Warren
592d201506cSStephen Warren        return RunAndLog(self, name, chained_file)
593d201506cSStephen Warren
594d201506cSStephen Warren    def write(self, stream, data, implicit=False):
595e8debf39SStephen Warren        """Write stream data into the log file.
596d201506cSStephen Warren
597d201506cSStephen Warren        This function should only be used by instances of LogfileStream or
598d201506cSStephen Warren        RunAndLog.
599d201506cSStephen Warren
600d201506cSStephen Warren        Args:
601d201506cSStephen Warren            stream: The stream whose data is being logged.
602d201506cSStephen Warren            data: The data to log.
603d201506cSStephen Warren            implicit: Boolean indicating whether data actually appeared in the
604d201506cSStephen Warren                stream, or was implicitly generated. A valid use-case is to
605d201506cSStephen Warren                repeat a shell prompt at the start of each separate log
606d201506cSStephen Warren                section, which makes the log sections more readable in
607d201506cSStephen Warren                isolation.
608d201506cSStephen Warren
609d201506cSStephen Warren        Returns:
610d201506cSStephen Warren            Nothing.
611e8debf39SStephen Warren        """
612d201506cSStephen Warren
613d201506cSStephen Warren        if stream != self.last_stream:
614d201506cSStephen Warren            self._terminate_stream()
61583357fd5SStephen Warren            self.f.write('<div class="stream block">\n')
61683357fd5SStephen Warren            self.f.write('<div class="stream-header block-header">Stream: ' +
61783357fd5SStephen Warren                         stream.name + '</div>\n')
61883357fd5SStephen Warren            self.f.write('<div class="stream-content block-content">\n')
619a2ec5606SStephen Warren            self.f.write('<pre>')
620d201506cSStephen Warren        if implicit:
621a2ec5606SStephen Warren            self.f.write('<span class="implicit">')
622d201506cSStephen Warren        self.f.write(self._escape(data))
623d201506cSStephen Warren        if implicit:
624a2ec5606SStephen Warren            self.f.write('</span>')
625d201506cSStephen Warren        self.last_stream = stream
626d201506cSStephen Warren
627d201506cSStephen Warren    def flush(self):
628e8debf39SStephen Warren        """Flush the log stream, to ensure correct log interleaving.
629d201506cSStephen Warren
630d201506cSStephen Warren        Args:
631d201506cSStephen Warren            None.
632d201506cSStephen Warren
633d201506cSStephen Warren        Returns:
634d201506cSStephen Warren            Nothing.
635e8debf39SStephen Warren        """
636d201506cSStephen Warren
637d201506cSStephen Warren        self.f.flush()
638