2 # Copyright (c) 2013-2016 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.
30 from __future__
import (
31 absolute_import
, division
, print_function
, unicode_literals
39 import simplejson
as json
44 from six
.moves
.BaseHTTPServer
import HTTPServer
, BaseHTTPRequestHandler
46 from framework
.core
import PIGLIT_CONFIG
47 from framework
import grouptools
49 __all__
= ['LogManager']
52 @six.add_metaclass(abc
.ABCMeta
)
53 class BaseLog(object):
54 """ Abstract base class for Log objects
56 It provides a lock, which should be used to lock whever the shared state is
57 modified, or when printing to the screen (or both).
60 state -- the state dict from LogManager
64 'pass', 'fail', 'warn', 'crash', 'skip', 'dmesg-warn', 'dmesg-fail',
65 'dry-run', 'timeout'])
68 def __init__(self
, state
, state_lock
):
69 self
._LOCK
= state_lock
71 self
._pad
= len(str(state
['total']))
74 def start(self
, name
):
75 """ Called before test run starts
77 This method is used to print things before the test starts
82 def log(self
, status
):
83 """ Print the result of the test
85 This method is run after the test completes, and is used to log the
86 actual result of the test
92 """ Print a final summary
94 This method is run at the end of the test run, and is used to print a
95 final summary of the run
100 class QuietLog(BaseLog
):
101 """ A logger providing minimal status output
103 This logger provides only a ninja like [x/y] pass: z, fail: w ... output.
105 It uses \r to print over the same line when printing to a tty. If
106 sys.stdout is not a tty then it prints \n at the end of the line instead
109 status -- the status to print
112 _test_counter
= itertools
.count()
114 def __init__(self
, *args
, **kwargs
):
115 super(QuietLog
, self
).__init
__(*args
, **kwargs
)
117 # If the output is a tty we can use '\r' (return, no linebreak) to
118 # print over the existing line, if stdout isn't a tty that will not
119 # work (and can break systems like jenkins), so we print a \n instead
120 if sys
.stdout
.isatty():
125 self
.__counter
= next(self
._test
_counter
)
127 def start(self
, name
):
128 # This cannot be done in the constructor, since the constructor gets
129 # called for the final summary too.
131 self
._state
['running'].append(self
.__counter
)
133 def _log(self
, status
):
134 """ non-locked helper for logging
136 To allow for code sharing with a subclass using a non-recursive lock we
137 need to share this code in a an unlocked form.
141 self
._state
['complete'] += 1
143 # Add to the summary dict
144 assert status
in self
.SUMMARY_KEYS
, \
145 'Invalid status for logger: {}'.format(status
)
146 self
._state
['summary'][status
] += 1
148 self
._print
_summary
()
149 self
._state
['running'].remove(self
.__counter
)
151 def log(self
, status
):
157 self
._print
_summary
()
160 def _print_summary(self
):
161 """ Print the summary result
163 this prints '[done/total] {status}', it is a private method hidden from
164 the children of this class (VerboseLog)
167 assert self
._LOCK
.locked()
169 out
= '[{done}/{total}] {status} {running}'.format(
170 done
=str(self
._state
['complete']).zfill(self
._pad
),
171 total
=str(self
._state
['total']).zfill(self
._pad
),
172 status
=', '.join('{0}: {1}'.format(k
, v
) for k
, v
in
173 sorted(six
.iteritems(self
._state
['summary']))),
174 running
=''.join('|/-\\'[x
% 4] for x
in self
._state
['running'])
179 def _print(self
, out
):
180 """ Shared print method that ensures any bad lines are overwritten """
181 assert self
._LOCK
.locked()
183 # If the new line is shorter than the old one add trailing
185 pad
= self
._state
['lastlength'] - len(out
)
187 sys
.stdout
.write(out
)
189 sys
.stdout
.write(' ' * pad
)
190 sys
.stdout
.write(self
._endcode
)
193 # Set the length of the line just printed (minus the spaces since
194 # we don't care if we leave whitespace) so that the next line will
195 # print extra whitespace if necissary
196 self
._state
['lastlength'] = len(out
)
199 class VerboseLog(QuietLog
):
200 """ A more verbose version of the QuietLog
202 This logger works like the quiet logger, except it doesn't overwrite the
203 previous line, ever. It also prints an additional line at the start of each
204 test, which the quite logger doesn't do.
207 def __init__(self
, *args
, **kwargs
):
208 super(VerboseLog
, self
).__init
__(*args
, **kwargs
)
209 self
.__name
= None # the name needs to be printed by start and log
211 def _print(self
, out
, newline
=False):
212 """ Print to the console, add a newline if necissary
214 For the verbose logger there are times that one wants both an
215 overwritten line, and a line that is static. This method adds the
216 ability to print a newline charcater at the end of the line.
218 This is useful for the non-status lines (running: <name>, and <status>:
219 <name>), since these lines should be be overwritten, but the running
223 super(VerboseLog
, self
)._print
(grouptools
.format(out
))
225 sys
.stdout
.write('\n')
228 def start(self
, name
):
229 """ Print the test that is being run next
231 This printings a running: <testname> message before the test run
235 super(VerboseLog
, self
).start(name
)
237 self
._print
('running: {}'.format(name
), newline
=True)
239 self
._print
_summary
()
241 def _log(self
, value
):
242 """ Print a message after the test finishes
244 This method prints <status>: <name>. It also does a little bit of magic
245 before calling super() to print the status line.
248 self
._print
('{0}: {1}'.format(value
, self
.__name
), newline
=True)
250 # Set lastlength to 0, this prevents printing needless padding in
252 self
._state
['lastlength'] = 0
253 super(VerboseLog
, self
)._log
(value
)
256 class DummyLog(BaseLog
):
257 """ A Logger that does nothing """
258 def __init__(self
, state
, state_lock
):
261 def start(self
, name
):
264 def log(self
, status
):
271 class HTTPLogServer(threading
.Thread
):
272 class RequestHandler(BaseHTTPRequestHandler
):
276 if self
.path
== "/summary":
277 self
.send_response(200)
279 with self
.server
.state_lock
:
281 "complete": self
.server
.state
["complete"],
282 "running" : self
.server
.state
["running"],
283 "total" : self
.server
.state
["total"],
284 "results" : self
.server
.state
["summary"],
286 self
.wfile
.write(json
.dumps(status
, indent
=self
.INDENT
))
288 self
.send_response(404)
291 def __init__(self
, state
, state_lock
):
292 super(HTTPLogServer
, self
).__init
__()
293 port
= int(PIGLIT_CONFIG
.safe_get("http", "port", fallback
=8080))
294 self
._httpd
= HTTPServer(("", port
), HTTPLogServer
.RequestHandler
)
295 self
._httpd
.state
= state
296 self
._httpd
.state_lock
= state_lock
300 with self
._httpd
.state_lock
:
301 # stop handling requests after the request for the final results
302 if self
._httpd
.state
["complete"] == self
._httpd
.state
["total"]:
304 self
._httpd
.handle_request()
307 class HTTPLog(BaseLog
):
308 """ A Logger that serves status information over http """
310 def __init__(self
, state
, state_lock
):
311 super(HTTPLog
, self
).__init
__(state
, state_lock
)
314 def start(self
, name
):
317 self
._state
['running'].append(self
._name
)
319 def log(self
, status
):
321 self
._state
['running'].remove(self
._name
)
322 self
._state
['complete'] += 1
323 assert status
in self
.SUMMARY_KEYS
324 self
._state
['summary'][str(status
)] += 1
330 class LogManager(object):
331 """ Creates new log objects
333 Each of the log objects it creates have two accessible methods: start() and
334 log(); neither method should return anything, and they must be thread safe.
335 log() should accept a single argument, which is the status the test
336 returned. start() should also take a single argument, the name of the test
338 When the LogManager is initialized it is initialized with an argument which
339 determines which Log class it will return (they are set via the LOG_MAP
340 dictionary, which maps string names to class callables), it will return
341 these as long as it exists.
344 logger -- a string name of a logger to use
345 total -- the total number of test to run
350 'verbose': VerboseLog
,
355 def __init__(self
, logger
, total
):
356 assert logger
in self
.LOG_MAP
357 self
._log
= self
.LOG_MAP
[logger
]
360 'summary': collections
.defaultdict(lambda: 0),
365 self
._state
_lock
= threading
.Lock()
367 # start the http server for http logger
369 self
.log_server
= HTTPLogServer(self
._state
, self
._state
_lock
)
370 self
.log_server
.start()
373 """ Return a new log instance """
374 return self
._log
(self
._state
, self
._state
_lock
)