2 # Copyright (c) 2013-2016, 2019 Intel Corporation
4 # Permission is hereby granted, free of charge, to any person obtaining a
5 # copy of this software and associated documentation files (the "Software"),
6 # to deal in the Software without restriction, including without limitation
7 # the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 # and/or sell copies of the Software, and to permit persons to whom the
9 # Software is furnished to do so, subject to the following conditions:
11 # The above copyright notice and this permission notice (including the next
12 # paragraph) shall be included in all copies or substantial portions of the
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
23 """ Module for terminal logging
25 This module provides a class LogManager, which acts as a state manager
26 returning BaseLog derived instances to individual tests.
35 from http
.server
import HTTPServer
, BaseHTTPRequestHandler
38 import simplejson
as json
42 from framework
.core
import PIGLIT_CONFIG
43 from framework
import grouptools
45 __all__
= ['LogManager']
48 class BaseLog(metaclass
=abc
.ABCMeta
):
49 """ Abstract base class for Log objects
51 It provides a lock, which should be used to lock whever the shared state is
52 modified, or when printing to the screen (or both).
55 state -- the state dict from LogManager
59 'pass', 'fail', 'warn', 'crash', 'skip', 'dmesg-warn', 'dmesg-fail',
60 'dry-run', 'timeout'])
63 def __init__(self
, state
, state_lock
):
64 self
._LOCK
= state_lock
66 self
._pad
= len(str(state
['total']))
69 def start(self
, name
):
70 """ Called before test run starts
72 This method is used to print things before the test starts
77 def log(self
, status
):
78 """ Print the result of the test
80 This method is run after the test completes, and is used to log the
81 actual result of the test
87 """ Print a final summary
89 This method is run at the end of the test run, and is used to print a
90 final summary of the run
95 class QuietLog(BaseLog
):
96 """ A logger providing minimal status output
98 This logger provides only a ninja like [x/y] pass: z, fail: w ... output.
100 It uses \r to print over the same line when printing to a tty. If
101 sys.stdout is not a tty then it prints \n at the end of the line instead
104 status -- the status to print
107 _test_counter
= itertools
.count()
109 def __init__(self
, *args
, **kwargs
):
110 super(QuietLog
, self
).__init
__(*args
, **kwargs
)
112 # If the output is a tty we can use '\r' (return, no linebreak) to
113 # print over the existing line, if stdout isn't a tty that will not
114 # work (and can break systems like jenkins), so we print a \n instead
115 if sys
.stdout
.isatty():
120 self
.__counter
= next(self
._test
_counter
)
122 def start(self
, name
):
123 # This cannot be done in the constructor, since the constructor gets
124 # called for the final summary too.
126 self
._state
['running'].append(self
.__counter
)
128 def _log(self
, status
):
129 """ non-locked helper for logging
131 To allow for code sharing with a subclass using a non-recursive lock we
132 need to share this code in a an unlocked form.
136 self
._state
['complete'] += 1
138 # Add to the summary dict
139 assert status
in self
.SUMMARY_KEYS
, \
140 'Invalid status for logger: {}'.format(status
)
141 self
._state
['summary'][status
] += 1
143 self
._print
_summary
()
144 self
._state
['running'].remove(self
.__counter
)
146 def log(self
, status
):
152 self
._print
_summary
()
155 def _print_summary(self
):
156 """ Print the summary result
158 this prints '[done/total] {status}', it is a private method hidden from
159 the children of this class (VerboseLog)
162 assert self
._LOCK
.locked()
164 out
= '[{done}/{total}] {status} {running}'.format(
165 done
=str(self
._state
['complete']).zfill(self
._pad
),
166 total
=str(self
._state
['total']).zfill(self
._pad
),
167 status
=', '.join('{0}: {1}'.format(k
, v
) for k
, v
in
168 sorted(self
._state
['summary'].items())),
169 running
=''.join('|/-\\'[x
% 4] for x
in self
._state
['running'])
174 def _print(self
, out
):
175 """ Shared print method that ensures any bad lines are overwritten """
176 assert self
._LOCK
.locked()
178 # If the new line is shorter than the old one add trailing
180 pad
= self
._state
['lastlength'] - len(out
)
182 sys
.stdout
.write(out
)
184 sys
.stdout
.write(' ' * pad
)
185 sys
.stdout
.write(self
._endcode
)
188 # Set the length of the line just printed (minus the spaces since
189 # we don't care if we leave whitespace) so that the next line will
190 # print extra whitespace if necissary
191 self
._state
['lastlength'] = len(out
)
194 class VerboseLog(QuietLog
):
195 """ A more verbose version of the QuietLog
197 This logger works like the quiet logger, except it doesn't overwrite the
198 previous line, ever. It also prints an additional line at the start of each
199 test, which the quite logger doesn't do.
202 def __init__(self
, *args
, **kwargs
):
203 super(VerboseLog
, self
).__init
__(*args
, **kwargs
)
204 self
.__name
= None # the name needs to be printed by start and log
206 def _print(self
, out
, newline
=False):
207 """ Print to the console, add a newline if necissary
209 For the verbose logger there are times that one wants both an
210 overwritten line, and a line that is static. This method adds the
211 ability to print a newline charcater at the end of the line.
213 This is useful for the non-status lines (running: <name>, and <status>:
214 <name>), since these lines should be be overwritten, but the running
218 super(VerboseLog
, self
)._print
(grouptools
.format(out
))
220 sys
.stdout
.write('\n')
223 def start(self
, name
):
224 """ Print the test that is being run next
226 This printings a running: <testname> message before the test run
230 super(VerboseLog
, self
).start(name
)
232 self
._print
('running: {}'.format(name
), newline
=True)
234 self
._print
_summary
()
236 def _log(self
, value
):
237 """ Print a message after the test finishes
239 This method prints <status>: <name>. It also does a little bit of magic
240 before calling super() to print the status line.
243 self
._print
('{0}: {1}'.format(value
, self
.__name
), newline
=True)
245 # Set lastlength to 0, this prevents printing needless padding in
247 self
._state
['lastlength'] = 0
248 super(VerboseLog
, self
)._log
(value
)
251 class DummyLog(BaseLog
):
252 """ A Logger that does nothing """
253 def __init__(self
, state
, state_lock
):
256 def start(self
, name
):
259 def log(self
, status
):
266 class HTTPLogServer(threading
.Thread
):
267 class RequestHandler(BaseHTTPRequestHandler
):
271 if self
.path
== "/summary":
272 self
.send_response(200)
274 with self
.server
.state_lock
:
276 "complete": self
.server
.state
["complete"],
277 "running" : self
.server
.state
["running"],
278 "total" : self
.server
.state
["total"],
279 "results" : self
.server
.state
["summary"],
281 self
.wfile
.write(json
.dumps(status
, indent
=self
.INDENT
))
283 self
.send_response(404)
286 def __init__(self
, state
, state_lock
):
287 super(HTTPLogServer
, self
).__init
__()
288 port
= int(PIGLIT_CONFIG
.safe_get("http", "port", fallback
=8080))
289 self
._httpd
= HTTPServer(("", port
), HTTPLogServer
.RequestHandler
)
290 self
._httpd
.state
= state
291 self
._httpd
.state_lock
= state_lock
295 with self
._httpd
.state_lock
:
296 # stop handling requests after the request for the final results
297 if self
._httpd
.state
["complete"] == self
._httpd
.state
["total"]:
299 self
._httpd
.handle_request()
302 class HTTPLog(BaseLog
):
303 """ A Logger that serves status information over http """
305 def __init__(self
, state
, state_lock
):
306 super(HTTPLog
, self
).__init
__(state
, state_lock
)
309 def start(self
, name
):
312 self
._state
['running'].append(self
._name
)
314 def log(self
, status
):
316 self
._state
['running'].remove(self
._name
)
317 self
._state
['complete'] += 1
318 assert status
in self
.SUMMARY_KEYS
319 self
._state
['summary'][str(status
)] += 1
325 class LogManager(object):
326 """ Creates new log objects
328 Each of the log objects it creates have two accessible methods: start() and
329 log(); neither method should return anything, and they must be thread safe.
330 log() should accept a single argument, which is the status the test
331 returned. start() should also take a single argument, the name of the test
333 When the LogManager is initialized it is initialized with an argument which
334 determines which Log class it will return (they are set via the LOG_MAP
335 dictionary, which maps string names to class callables), it will return
336 these as long as it exists.
339 logger -- a string name of a logger to use
340 total -- the total number of test to run
345 'verbose': VerboseLog
,
350 def __init__(self
, logger
, total
):
351 assert logger
in self
.LOG_MAP
352 self
._log
= self
.LOG_MAP
[logger
]
355 'summary': collections
.defaultdict(lambda: 0),
360 self
._state
_lock
= threading
.Lock()
362 # start the http server for http logger
364 self
.log_server
= HTTPLogServer(self
._state
, self
._state
_lock
)
365 self
.log_server
.start()
368 """ Return a new log instance """
369 return self
._log
(self
._state
, self
._state
_lock
)