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