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