xref: /openbmc/u-boot/test/py/multiplexed_log.py (revision 3b8d9d977b6dd6d04f0cfe2eb5dce25264fe40f5)
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:
122*3b8d9d97SSimon Glass            The output as a string.
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
164*3b8d9d97SSimon Glass        return output
165d201506cSStephen Warren
166d201506cSStephen Warrenclass SectionCtxMgr(object):
167e8debf39SStephen Warren    """A context manager for Python's "with" statement, which allows a certain
168d201506cSStephen Warren    portion of test code to be logged to a separate section of the log file.
169d201506cSStephen Warren    Objects of this type should be created by factory functions in the Logfile
170e8debf39SStephen Warren    class rather than directly."""
171d201506cSStephen Warren
17283357fd5SStephen Warren    def __init__(self, log, marker, anchor):
173e8debf39SStephen Warren        """Initialize a new object.
174d201506cSStephen Warren
175d201506cSStephen Warren        Args:
176d201506cSStephen Warren            log: The Logfile object to log to.
177d201506cSStephen Warren            marker: The name of the nested log section.
17883357fd5SStephen Warren            anchor: The anchor value to pass to start_section().
179d201506cSStephen Warren
180d201506cSStephen Warren        Returns:
181d201506cSStephen Warren            Nothing.
182e8debf39SStephen Warren        """
183d201506cSStephen Warren
184d201506cSStephen Warren        self.log = log
185d201506cSStephen Warren        self.marker = marker
18683357fd5SStephen Warren        self.anchor = anchor
187d201506cSStephen Warren
188d201506cSStephen Warren    def __enter__(self):
18983357fd5SStephen Warren        self.anchor = self.log.start_section(self.marker, self.anchor)
190d201506cSStephen Warren
191d201506cSStephen Warren    def __exit__(self, extype, value, traceback):
192d201506cSStephen Warren        self.log.end_section(self.marker)
193d201506cSStephen Warren
194d201506cSStephen Warrenclass Logfile(object):
195e8debf39SStephen Warren    """Generates an HTML-formatted log file containing multiple streams of
196e8debf39SStephen Warren    data, each represented in a well-delineated/-structured fashion."""
197d201506cSStephen Warren
198d201506cSStephen Warren    def __init__(self, fn):
199e8debf39SStephen Warren        """Initialize a new object.
200d201506cSStephen Warren
201d201506cSStephen Warren        Args:
202d201506cSStephen Warren            fn: The filename to write to.
203d201506cSStephen Warren
204d201506cSStephen Warren        Returns:
205d201506cSStephen Warren            Nothing.
206e8debf39SStephen Warren        """
207d201506cSStephen Warren
208a2ec5606SStephen Warren        self.f = open(fn, 'wt')
209d201506cSStephen Warren        self.last_stream = None
210d201506cSStephen Warren        self.blocks = []
211d201506cSStephen Warren        self.cur_evt = 1
21283357fd5SStephen Warren        self.anchor = 0
21383357fd5SStephen Warren
214a2ec5606SStephen Warren        shutil.copy(mod_dir + '/multiplexed_log.css', os.path.dirname(fn))
215a2ec5606SStephen Warren        self.f.write('''\
216d201506cSStephen Warren<html>
217d201506cSStephen Warren<head>
218d201506cSStephen Warren<link rel="stylesheet" type="text/css" href="multiplexed_log.css">
21983357fd5SStephen Warren<script src="http://code.jquery.com/jquery.min.js"></script>
22083357fd5SStephen Warren<script>
22183357fd5SStephen Warren$(document).ready(function () {
22283357fd5SStephen Warren    // Copy status report HTML to start of log for easy access
22383357fd5SStephen Warren    sts = $(".block#status_report")[0].outerHTML;
22483357fd5SStephen Warren    $("tt").prepend(sts);
22583357fd5SStephen Warren
22683357fd5SStephen Warren    // Add expand/contract buttons to all block headers
22783357fd5SStephen Warren    btns = "<span class=\\\"block-expand hidden\\\">[+] </span>" +
22883357fd5SStephen Warren        "<span class=\\\"block-contract\\\">[-] </span>";
22983357fd5SStephen Warren    $(".block-header").prepend(btns);
23083357fd5SStephen Warren
23183357fd5SStephen Warren    // Pre-contract all blocks which passed, leaving only problem cases
23283357fd5SStephen Warren    // expanded, to highlight issues the user should look at.
23383357fd5SStephen Warren    // Only top-level blocks (sections) should have any status
23483357fd5SStephen Warren    passed_bcs = $(".block-content:has(.status-pass)");
23583357fd5SStephen Warren    // Some blocks might have multiple status entries (e.g. the status
23683357fd5SStephen Warren    // report), so take care not to hide blocks with partial success.
23783357fd5SStephen Warren    passed_bcs = passed_bcs.not(":has(.status-fail)");
23883357fd5SStephen Warren    passed_bcs = passed_bcs.not(":has(.status-xfail)");
23983357fd5SStephen Warren    passed_bcs = passed_bcs.not(":has(.status-xpass)");
24083357fd5SStephen Warren    passed_bcs = passed_bcs.not(":has(.status-skipped)");
24183357fd5SStephen Warren    // Hide the passed blocks
24283357fd5SStephen Warren    passed_bcs.addClass("hidden");
24383357fd5SStephen Warren    // Flip the expand/contract button hiding for those blocks.
24483357fd5SStephen Warren    bhs = passed_bcs.parent().children(".block-header")
24583357fd5SStephen Warren    bhs.children(".block-expand").removeClass("hidden");
24683357fd5SStephen Warren    bhs.children(".block-contract").addClass("hidden");
24783357fd5SStephen Warren
24883357fd5SStephen Warren    // Add click handler to block headers.
24983357fd5SStephen Warren    // The handler expands/contracts the block.
25083357fd5SStephen Warren    $(".block-header").on("click", function (e) {
25183357fd5SStephen Warren        var header = $(this);
25283357fd5SStephen Warren        var content = header.next(".block-content");
25383357fd5SStephen Warren        var expanded = !content.hasClass("hidden");
25483357fd5SStephen Warren        if (expanded) {
25583357fd5SStephen Warren            content.addClass("hidden");
25683357fd5SStephen Warren            header.children(".block-expand").first().removeClass("hidden");
25783357fd5SStephen Warren            header.children(".block-contract").first().addClass("hidden");
25883357fd5SStephen Warren        } else {
25983357fd5SStephen Warren            header.children(".block-contract").first().removeClass("hidden");
26083357fd5SStephen Warren            header.children(".block-expand").first().addClass("hidden");
26183357fd5SStephen Warren            content.removeClass("hidden");
26283357fd5SStephen Warren        }
26383357fd5SStephen Warren    });
26483357fd5SStephen Warren
26583357fd5SStephen Warren    // When clicking on a link, expand the target block
26683357fd5SStephen Warren    $("a").on("click", function (e) {
26783357fd5SStephen Warren        var block = $($(this).attr("href"));
26883357fd5SStephen Warren        var header = block.children(".block-header");
26983357fd5SStephen Warren        var content = block.children(".block-content").first();
27083357fd5SStephen Warren        header.children(".block-contract").first().removeClass("hidden");
27183357fd5SStephen Warren        header.children(".block-expand").first().addClass("hidden");
27283357fd5SStephen Warren        content.removeClass("hidden");
27383357fd5SStephen Warren    });
27483357fd5SStephen Warren});
27583357fd5SStephen Warren</script>
276d201506cSStephen Warren</head>
277d201506cSStephen Warren<body>
278d201506cSStephen Warren<tt>
279a2ec5606SStephen Warren''')
280d201506cSStephen Warren
281d201506cSStephen Warren    def close(self):
282e8debf39SStephen Warren        """Close the log file.
283d201506cSStephen Warren
284d201506cSStephen Warren        After calling this function, no more data may be written to the log.
285d201506cSStephen Warren
286d201506cSStephen Warren        Args:
287d201506cSStephen Warren            None.
288d201506cSStephen Warren
289d201506cSStephen Warren        Returns:
290d201506cSStephen Warren            Nothing.
291e8debf39SStephen Warren        """
292d201506cSStephen Warren
293a2ec5606SStephen Warren        self.f.write('''\
294d201506cSStephen Warren</tt>
295d201506cSStephen Warren</body>
296d201506cSStephen Warren</html>
297a2ec5606SStephen Warren''')
298d201506cSStephen Warren        self.f.close()
299d201506cSStephen Warren
300d201506cSStephen Warren    # The set of characters that should be represented as hexadecimal codes in
301d201506cSStephen Warren    # the log file.
302a2ec5606SStephen Warren    _nonprint = ('%' + ''.join(chr(c) for c in range(0, 32) if c not in (9, 10)) +
303a2ec5606SStephen Warren                 ''.join(chr(c) for c in range(127, 256)))
304d201506cSStephen Warren
305d201506cSStephen Warren    def _escape(self, data):
306e8debf39SStephen Warren        """Render data format suitable for inclusion in an HTML document.
307d201506cSStephen Warren
308d201506cSStephen Warren        This includes HTML-escaping certain characters, and translating
309d201506cSStephen Warren        control characters to a hexadecimal representation.
310d201506cSStephen Warren
311d201506cSStephen Warren        Args:
312d201506cSStephen Warren            data: The raw string data to be escaped.
313d201506cSStephen Warren
314d201506cSStephen Warren        Returns:
315d201506cSStephen Warren            An escaped version of the data.
316e8debf39SStephen Warren        """
317d201506cSStephen Warren
318a2ec5606SStephen Warren        data = data.replace(chr(13), '')
319a2ec5606SStephen Warren        data = ''.join((c in self._nonprint) and ('%%%02x' % ord(c)) or
320d201506cSStephen Warren                       c for c in data)
321d201506cSStephen Warren        data = cgi.escape(data)
322d201506cSStephen Warren        return data
323d201506cSStephen Warren
324d201506cSStephen Warren    def _terminate_stream(self):
325e8debf39SStephen Warren        """Write HTML to the log file to terminate the current stream's data.
326d201506cSStephen Warren
327d201506cSStephen Warren        Args:
328d201506cSStephen Warren            None.
329d201506cSStephen Warren
330d201506cSStephen Warren        Returns:
331d201506cSStephen Warren            Nothing.
332e8debf39SStephen Warren        """
333d201506cSStephen Warren
334d201506cSStephen Warren        self.cur_evt += 1
335d201506cSStephen Warren        if not self.last_stream:
336d201506cSStephen Warren            return
337a2ec5606SStephen Warren        self.f.write('</pre>\n')
33883357fd5SStephen Warren        self.f.write('<div class="stream-trailer block-trailer">End stream: ' +
339a2ec5606SStephen Warren                     self.last_stream.name + '</div>\n')
340a2ec5606SStephen Warren        self.f.write('</div>\n')
34183357fd5SStephen Warren        self.f.write('</div>\n')
342d201506cSStephen Warren        self.last_stream = None
343d201506cSStephen Warren
34483357fd5SStephen Warren    def _note(self, note_type, msg, anchor=None):
345e8debf39SStephen Warren        """Write a note or one-off message to the log file.
346d201506cSStephen Warren
347d201506cSStephen Warren        Args:
348d201506cSStephen Warren            note_type: The type of note. This must be a value supported by the
349d201506cSStephen Warren                accompanying multiplexed_log.css.
350d201506cSStephen Warren            msg: The note/message to log.
35183357fd5SStephen Warren            anchor: Optional internal link target.
352d201506cSStephen Warren
353d201506cSStephen Warren        Returns:
354d201506cSStephen Warren            Nothing.
355e8debf39SStephen Warren        """
356d201506cSStephen Warren
357d201506cSStephen Warren        self._terminate_stream()
35883357fd5SStephen Warren        self.f.write('<div class="' + note_type + '">\n')
35983357fd5SStephen Warren        if anchor:
36083357fd5SStephen Warren            self.f.write('<a href="#%s">\n' % anchor)
36183357fd5SStephen Warren        self.f.write('<pre>')
362d201506cSStephen Warren        self.f.write(self._escape(msg))
36383357fd5SStephen Warren        self.f.write('\n</pre>\n')
36483357fd5SStephen Warren        if anchor:
36583357fd5SStephen Warren            self.f.write('</a>\n')
36683357fd5SStephen Warren        self.f.write('</div>\n')
367d201506cSStephen Warren
36883357fd5SStephen Warren    def start_section(self, marker, anchor=None):
369e8debf39SStephen Warren        """Begin a new nested section in the log file.
370d201506cSStephen Warren
371d201506cSStephen Warren        Args:
372d201506cSStephen Warren            marker: The name of the section that is starting.
37383357fd5SStephen Warren            anchor: The value to use for the anchor. If None, a unique value
37483357fd5SStephen Warren              will be calculated and used
375d201506cSStephen Warren
376d201506cSStephen Warren        Returns:
37783357fd5SStephen Warren            Name of the HTML anchor emitted before section.
378e8debf39SStephen Warren        """
379d201506cSStephen Warren
380d201506cSStephen Warren        self._terminate_stream()
381d201506cSStephen Warren        self.blocks.append(marker)
38283357fd5SStephen Warren        if not anchor:
38383357fd5SStephen Warren            self.anchor += 1
38483357fd5SStephen Warren            anchor = str(self.anchor)
385a2ec5606SStephen Warren        blk_path = '/'.join(self.blocks)
38683357fd5SStephen Warren        self.f.write('<div class="section block" id="' + anchor + '">\n')
38783357fd5SStephen Warren        self.f.write('<div class="section-header block-header">Section: ' +
38883357fd5SStephen Warren                     blk_path + '</div>\n')
38983357fd5SStephen Warren        self.f.write('<div class="section-content block-content">\n')
39083357fd5SStephen Warren
39183357fd5SStephen Warren        return anchor
392d201506cSStephen Warren
393d201506cSStephen Warren    def end_section(self, marker):
394e8debf39SStephen Warren        """Terminate the current nested section in the log file.
395d201506cSStephen Warren
396d201506cSStephen Warren        This function validates proper nesting of start_section() and
397d201506cSStephen Warren        end_section() calls. If a mismatch is found, an exception is raised.
398d201506cSStephen Warren
399d201506cSStephen Warren        Args:
400d201506cSStephen Warren            marker: The name of the section that is ending.
401d201506cSStephen Warren
402d201506cSStephen Warren        Returns:
403d201506cSStephen Warren            Nothing.
404e8debf39SStephen Warren        """
405d201506cSStephen Warren
406d201506cSStephen Warren        if (not self.blocks) or (marker != self.blocks[-1]):
407a2ec5606SStephen Warren            raise Exception('Block nesting mismatch: "%s" "%s"' %
408a2ec5606SStephen Warren                            (marker, '/'.join(self.blocks)))
409d201506cSStephen Warren        self._terminate_stream()
410a2ec5606SStephen Warren        blk_path = '/'.join(self.blocks)
41183357fd5SStephen Warren        self.f.write('<div class="section-trailer block-trailer">' +
41283357fd5SStephen Warren                     'End section: ' + blk_path + '</div>\n')
41383357fd5SStephen Warren        self.f.write('</div>\n')
414a2ec5606SStephen Warren        self.f.write('</div>\n')
415d201506cSStephen Warren        self.blocks.pop()
416d201506cSStephen Warren
41783357fd5SStephen Warren    def section(self, marker, anchor=None):
418e8debf39SStephen Warren        """Create a temporary section in the log file.
419d201506cSStephen Warren
420d201506cSStephen Warren        This function creates a context manager for Python's "with" statement,
421d201506cSStephen Warren        which allows a certain portion of test code to be logged to a separate
422d201506cSStephen Warren        section of the log file.
423d201506cSStephen Warren
424d201506cSStephen Warren        Usage:
425d201506cSStephen Warren            with log.section("somename"):
426d201506cSStephen Warren                some test code
427d201506cSStephen Warren
428d201506cSStephen Warren        Args:
429d201506cSStephen Warren            marker: The name of the nested section.
43083357fd5SStephen Warren            anchor: The anchor value to pass to start_section().
431d201506cSStephen Warren
432d201506cSStephen Warren        Returns:
433d201506cSStephen Warren            A context manager object.
434e8debf39SStephen Warren        """
435d201506cSStephen Warren
43683357fd5SStephen Warren        return SectionCtxMgr(self, marker, anchor)
437d201506cSStephen Warren
438d201506cSStephen Warren    def error(self, msg):
439e8debf39SStephen Warren        """Write an error note to the log file.
440d201506cSStephen Warren
441d201506cSStephen Warren        Args:
442d201506cSStephen Warren            msg: A message describing the error.
443d201506cSStephen Warren
444d201506cSStephen Warren        Returns:
445d201506cSStephen Warren            Nothing.
446e8debf39SStephen Warren        """
447d201506cSStephen Warren
448d201506cSStephen Warren        self._note("error", msg)
449d201506cSStephen Warren
450d201506cSStephen Warren    def warning(self, msg):
451e8debf39SStephen Warren        """Write an warning note to the log file.
452d201506cSStephen Warren
453d201506cSStephen Warren        Args:
454d201506cSStephen Warren            msg: A message describing the warning.
455d201506cSStephen Warren
456d201506cSStephen Warren        Returns:
457d201506cSStephen Warren            Nothing.
458e8debf39SStephen Warren        """
459d201506cSStephen Warren
460d201506cSStephen Warren        self._note("warning", msg)
461d201506cSStephen Warren
462d201506cSStephen Warren    def info(self, msg):
463e8debf39SStephen Warren        """Write an informational note to the log file.
464d201506cSStephen Warren
465d201506cSStephen Warren        Args:
466d201506cSStephen Warren            msg: An informational message.
467d201506cSStephen Warren
468d201506cSStephen Warren        Returns:
469d201506cSStephen Warren            Nothing.
470e8debf39SStephen Warren        """
471d201506cSStephen Warren
472d201506cSStephen Warren        self._note("info", msg)
473d201506cSStephen Warren
474d201506cSStephen Warren    def action(self, msg):
475e8debf39SStephen Warren        """Write an action note to the log file.
476d201506cSStephen Warren
477d201506cSStephen Warren        Args:
478d201506cSStephen Warren            msg: A message describing the action that is being logged.
479d201506cSStephen Warren
480d201506cSStephen Warren        Returns:
481d201506cSStephen Warren            Nothing.
482e8debf39SStephen Warren        """
483d201506cSStephen Warren
484d201506cSStephen Warren        self._note("action", msg)
485d201506cSStephen Warren
48683357fd5SStephen Warren    def status_pass(self, msg, anchor=None):
487e8debf39SStephen Warren        """Write a note to the log file describing test(s) which passed.
488d201506cSStephen Warren
489d201506cSStephen Warren        Args:
49078b39cc3SStephen Warren            msg: A message describing the passed test(s).
49183357fd5SStephen Warren            anchor: Optional internal link target.
492d201506cSStephen Warren
493d201506cSStephen Warren        Returns:
494d201506cSStephen Warren            Nothing.
495e8debf39SStephen Warren        """
496d201506cSStephen Warren
49783357fd5SStephen Warren        self._note("status-pass", msg, anchor)
498d201506cSStephen Warren
49983357fd5SStephen Warren    def status_skipped(self, msg, anchor=None):
500e8debf39SStephen Warren        """Write a note to the log file describing skipped test(s).
501d201506cSStephen Warren
502d201506cSStephen Warren        Args:
50378b39cc3SStephen Warren            msg: A message describing the skipped test(s).
50483357fd5SStephen Warren            anchor: Optional internal link target.
505d201506cSStephen Warren
506d201506cSStephen Warren        Returns:
507d201506cSStephen Warren            Nothing.
508e8debf39SStephen Warren        """
509d201506cSStephen Warren
51083357fd5SStephen Warren        self._note("status-skipped", msg, anchor)
511d201506cSStephen Warren
51283357fd5SStephen Warren    def status_xfail(self, msg, anchor=None):
51378b39cc3SStephen Warren        """Write a note to the log file describing xfailed test(s).
51478b39cc3SStephen Warren
51578b39cc3SStephen Warren        Args:
51678b39cc3SStephen Warren            msg: A message describing the xfailed test(s).
51783357fd5SStephen Warren            anchor: Optional internal link target.
51878b39cc3SStephen Warren
51978b39cc3SStephen Warren        Returns:
52078b39cc3SStephen Warren            Nothing.
52178b39cc3SStephen Warren        """
52278b39cc3SStephen Warren
52383357fd5SStephen Warren        self._note("status-xfail", msg, anchor)
52478b39cc3SStephen Warren
52583357fd5SStephen Warren    def status_xpass(self, msg, anchor=None):
52678b39cc3SStephen Warren        """Write a note to the log file describing xpassed test(s).
52778b39cc3SStephen Warren
52878b39cc3SStephen Warren        Args:
52978b39cc3SStephen Warren            msg: A message describing the xpassed test(s).
53083357fd5SStephen Warren            anchor: Optional internal link target.
53178b39cc3SStephen Warren
53278b39cc3SStephen Warren        Returns:
53378b39cc3SStephen Warren            Nothing.
53478b39cc3SStephen Warren        """
53578b39cc3SStephen Warren
53683357fd5SStephen Warren        self._note("status-xpass", msg, anchor)
53778b39cc3SStephen Warren
53883357fd5SStephen Warren    def status_fail(self, msg, anchor=None):
539e8debf39SStephen Warren        """Write a note to the log file describing failed test(s).
540d201506cSStephen Warren
541d201506cSStephen Warren        Args:
54278b39cc3SStephen Warren            msg: A message describing the failed test(s).
54383357fd5SStephen Warren            anchor: Optional internal link target.
544d201506cSStephen Warren
545d201506cSStephen Warren        Returns:
546d201506cSStephen Warren            Nothing.
547e8debf39SStephen Warren        """
548d201506cSStephen Warren
54983357fd5SStephen Warren        self._note("status-fail", msg, anchor)
550d201506cSStephen Warren
551d201506cSStephen Warren    def get_stream(self, name, chained_file=None):
552e8debf39SStephen Warren        """Create an object to log a single stream's data into the log file.
553d201506cSStephen Warren
554d201506cSStephen Warren        This creates a "file-like" object that can be written to in order to
555d201506cSStephen Warren        write a single stream's data to the log file. The implementation will
556d201506cSStephen Warren        handle any required interleaving of data (from multiple streams) in
557d201506cSStephen Warren        the log, in a way that makes it obvious which stream each bit of data
558d201506cSStephen Warren        came from.
559d201506cSStephen Warren
560d201506cSStephen Warren        Args:
561d201506cSStephen Warren            name: The name of the stream.
562d201506cSStephen Warren            chained_file: The file-like object to which all stream data should
563d201506cSStephen Warren                be logged to in addition to this log. Can be None.
564d201506cSStephen Warren
565d201506cSStephen Warren        Returns:
566d201506cSStephen Warren            A file-like object.
567e8debf39SStephen Warren        """
568d201506cSStephen Warren
569d201506cSStephen Warren        return LogfileStream(self, name, chained_file)
570d201506cSStephen Warren
571d201506cSStephen Warren    def get_runner(self, name, chained_file=None):
572e8debf39SStephen Warren        """Create an object that executes processes and logs their output.
573d201506cSStephen Warren
574d201506cSStephen Warren        Args:
575d201506cSStephen Warren            name: The name of this sub-process.
576d201506cSStephen Warren            chained_file: The file-like object to which all stream data should
577d201506cSStephen Warren                be logged to in addition to logfile. Can be None.
578d201506cSStephen Warren
579d201506cSStephen Warren        Returns:
580d201506cSStephen Warren            A RunAndLog object.
581e8debf39SStephen Warren        """
582d201506cSStephen Warren
583d201506cSStephen Warren        return RunAndLog(self, name, chained_file)
584d201506cSStephen Warren
585d201506cSStephen Warren    def write(self, stream, data, implicit=False):
586e8debf39SStephen Warren        """Write stream data into the log file.
587d201506cSStephen Warren
588d201506cSStephen Warren        This function should only be used by instances of LogfileStream or
589d201506cSStephen Warren        RunAndLog.
590d201506cSStephen Warren
591d201506cSStephen Warren        Args:
592d201506cSStephen Warren            stream: The stream whose data is being logged.
593d201506cSStephen Warren            data: The data to log.
594d201506cSStephen Warren            implicit: Boolean indicating whether data actually appeared in the
595d201506cSStephen Warren                stream, or was implicitly generated. A valid use-case is to
596d201506cSStephen Warren                repeat a shell prompt at the start of each separate log
597d201506cSStephen Warren                section, which makes the log sections more readable in
598d201506cSStephen Warren                isolation.
599d201506cSStephen Warren
600d201506cSStephen Warren        Returns:
601d201506cSStephen Warren            Nothing.
602e8debf39SStephen Warren        """
603d201506cSStephen Warren
604d201506cSStephen Warren        if stream != self.last_stream:
605d201506cSStephen Warren            self._terminate_stream()
60683357fd5SStephen Warren            self.f.write('<div class="stream block">\n')
60783357fd5SStephen Warren            self.f.write('<div class="stream-header block-header">Stream: ' +
60883357fd5SStephen Warren                         stream.name + '</div>\n')
60983357fd5SStephen Warren            self.f.write('<div class="stream-content block-content">\n')
610a2ec5606SStephen Warren            self.f.write('<pre>')
611d201506cSStephen Warren        if implicit:
612a2ec5606SStephen Warren            self.f.write('<span class="implicit">')
613d201506cSStephen Warren        self.f.write(self._escape(data))
614d201506cSStephen Warren        if implicit:
615a2ec5606SStephen Warren            self.f.write('</span>')
616d201506cSStephen Warren        self.last_stream = stream
617d201506cSStephen Warren
618d201506cSStephen Warren    def flush(self):
619e8debf39SStephen Warren        """Flush the log stream, to ensure correct log interleaving.
620d201506cSStephen Warren
621d201506cSStephen Warren        Args:
622d201506cSStephen Warren            None.
623d201506cSStephen Warren
624d201506cSStephen Warren        Returns:
625d201506cSStephen Warren            Nothing.
626e8debf39SStephen Warren        """
627d201506cSStephen Warren
628d201506cSStephen Warren        self.f.flush()
629