2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
4
# Copyright 2010 United States Government as represented by the
5
# Administrator of the National Aeronautics and Space Administration.
8
# Licensed under the Apache License, Version 2.0 (the "License");
9
# you may not use this file except in compliance with the License.
10
# You may obtain a copy of the License at
12
# http://www.apache.org/licenses/LICENSE-2.0
14
# Unless required by applicable law or agreed to in writing, software
15
# distributed under the License is distributed on an "AS IS" BASIS,
16
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
# See the License for the specific language governing permissions and
18
# limitations under the License.
20
# Colorizer Code is borrowed from Twisted:
21
# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
23
# Permission is hereby granted, free of charge, to any person obtaining
24
# a copy of this software and associated documentation files (the
25
# "Software"), to deal in the Software without restriction, including
26
# without limitation the rights to use, copy, modify, merge, publish,
27
# distribute, sublicense, and/or sell copies of the Software, and to
28
# permit persons to whom the Software is furnished to do so, subject to
29
# the following conditions:
31
# The above copyright notice and this permission notice shall be
32
# included in all copies or substantial portions of the Software.
34
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
35
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
36
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
37
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
38
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
39
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
40
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
41
"""Unittest runner for Nova.
44
python nova/testing/runner.py
46
To run a single test module:
47
python nova/testing/runner.py test_compute
51
python nova/testing/runner.py api.test_wsgi
54
python nova/testing/runner.py
55
test_compute:ComputeTestCase.test_run_terminate
67
from nose import config
69
from nose import result
71
gettext.install('nova', unicode=1)
72
reldir = os.path.join(os.path.dirname(__file__), '..', '..')
73
absdir = os.path.abspath(reldir)
74
sys.path.insert(0, absdir)
76
from nova import flags
77
from nova import log as logging
78
from nova.openstack.common import cfg
81
class _AnsiColorizer(object):
83
A colorizer is an object that loosely wraps around a stream, allowing
84
callers to write text to the stream in a particular color.
86
Colorizer classes must implement C{supported()} and C{write(text, color)}.
88
_colors = dict(black=30, red=31, green=32, yellow=33,
89
blue=34, magenta=35, cyan=36, white=37)
91
def __init__(self, stream):
94
def supported(cls, stream=sys.stdout):
96
A class method that returns True if the current platform supports
97
coloring terminal output using this method. Returns False otherwise.
99
if not stream.isatty():
100
return False # auto color only on TTYs
108
return curses.tigetnum("colors") > 2
111
return curses.tigetnum("colors") > 2
114
supported = classmethod(supported)
116
def write(self, text, color):
118
Write the given text to the stream in the given color.
120
@param text: Text to be written to the stream.
122
@param color: A string label for a color. e.g. 'red', 'white'.
124
color = self._colors[color]
125
self.stream.write('\x1b[%s;1m%s\x1b[0m' % (color, text))
128
class _Win32Colorizer(object):
130
See _AnsiColorizer docstring.
132
def __init__(self, stream):
133
import win32console as win
134
red, green, blue, bold = (win.FOREGROUND_RED, win.FOREGROUND_GREEN,
135
win.FOREGROUND_BLUE, win.FOREGROUND_INTENSITY)
137
self.screenBuffer = win.GetStdHandle(win.STD_OUT_HANDLE)
139
'normal': red | green | blue,
141
'green': green | bold,
143
'yellow': red | green | bold,
144
'magenta': red | blue | bold,
145
'cyan': green | blue | bold,
146
'white': red | green | blue | bold
149
def supported(cls, stream=sys.stdout):
152
screenBuffer = win32console.GetStdHandle(
153
win32console.STD_OUT_HANDLE)
158
screenBuffer.SetConsoleTextAttribute(
159
win32console.FOREGROUND_RED |
160
win32console.FOREGROUND_GREEN |
161
win32console.FOREGROUND_BLUE)
162
except pywintypes.error:
166
supported = classmethod(supported)
168
def write(self, text, color):
169
color = self._colors[color]
170
self.screenBuffer.SetConsoleTextAttribute(color)
171
self.stream.write(text)
172
self.screenBuffer.SetConsoleTextAttribute(self._colors['normal'])
175
class _NullColorizer(object):
177
See _AnsiColorizer docstring.
179
def __init__(self, stream):
182
def supported(cls, stream=sys.stdout):
184
supported = classmethod(supported)
186
def write(self, text, color):
187
self.stream.write(text)
190
def get_elapsed_time_color(elapsed_time):
191
if elapsed_time > 1.0:
193
elif elapsed_time > 0.25:
199
class NovaTestResult(result.TextTestResult):
200
def __init__(self, *args, **kw):
201
self.show_elapsed = kw.pop('show_elapsed')
202
result.TextTestResult.__init__(self, *args, **kw)
203
self.num_slow_tests = 5
204
self.slow_tests = [] # this is a fixed-sized heap
205
self._last_case = None
206
self.colorizer = None
207
# NOTE(vish): reset stdout for the terminal check
209
sys.stdout = sys.__stdout__
210
for colorizer in [_Win32Colorizer, _AnsiColorizer, _NullColorizer]:
211
if colorizer.supported():
212
self.colorizer = colorizer(self.stream)
216
# NOTE(lorinh): Initialize start_time in case a sqlalchemy-migrate
217
# error results in it failing to be initialized later. Otherwise,
218
# _handleElapsedTime will fail, causing the wrong error message to
220
self.start_time = time.time()
222
def getDescription(self, test):
225
def _handleElapsedTime(self, test):
226
self.elapsed_time = time.time() - self.start_time
227
item = (self.elapsed_time, test)
228
# Record only the n-slowest tests using heap
229
if len(self.slow_tests) >= self.num_slow_tests:
230
heapq.heappushpop(self.slow_tests, item)
232
heapq.heappush(self.slow_tests, item)
234
def _writeElapsedTime(self, test):
235
color = get_elapsed_time_color(self.elapsed_time)
236
self.colorizer.write(" %.2f" % self.elapsed_time, color)
238
def _writeResult(self, test, long_result, color, short_result, success):
240
self.colorizer.write(long_result, color)
241
if self.show_elapsed and success:
242
self._writeElapsedTime(test)
243
self.stream.writeln()
245
self.stream.write(short_result)
248
# NOTE(vish): copied from unittest with edit to add color
249
def addSuccess(self, test):
250
unittest.TestResult.addSuccess(self, test)
251
self._handleElapsedTime(test)
252
self._writeResult(test, 'OK', 'green', '.', True)
254
# NOTE(vish): copied from unittest with edit to add color
255
def addFailure(self, test, err):
256
unittest.TestResult.addFailure(self, test, err)
257
self._handleElapsedTime(test)
258
self._writeResult(test, 'FAIL', 'red', 'F', False)
260
# NOTE(vish): copied from nose with edit to add color
261
def addError(self, test, err):
262
"""Overrides normal addError to add support for
263
errorClasses. If the exception is a registered class, the
264
error will be added to the list for that class, not errors.
266
self._handleElapsedTime(test)
267
stream = getattr(self, 'stream', None)
270
exc_info = self._exc_info_to_string(err, test)
273
exc_info = self._exc_info_to_string(err)
274
for cls, (storage, label, isfail) in self.errorClasses.items():
275
if result.isclass(ec) and issubclass(ec, cls):
278
storage.append((test, exc_info))
279
# Might get patched into a streamless result
280
if stream is not None:
283
detail = result._exception_detail(err[1])
285
message.append(detail)
286
stream.writeln(": ".join(message))
288
stream.write(label[:1])
290
self.errors.append((test, exc_info))
292
if stream is not None:
293
self._writeResult(test, 'ERROR', 'red', 'E', False)
295
def startTest(self, test):
296
unittest.TestResult.startTest(self, test)
297
self.start_time = time.time()
298
current_case = test.test.__class__.__name__
301
if current_case != self._last_case:
302
self.stream.writeln(current_case)
303
self._last_case = current_case
306
' %s' % str(test.test._testMethodName).ljust(60))
310
class NovaTestRunner(core.TextTestRunner):
311
def __init__(self, *args, **kwargs):
312
self.show_elapsed = kwargs.pop('show_elapsed')
313
core.TextTestRunner.__init__(self, *args, **kwargs)
315
def _makeResult(self):
316
return NovaTestResult(self.stream,
320
show_elapsed=self.show_elapsed)
322
def _writeSlowTests(self, result_):
323
# Pare out 'fast' tests
324
slow_tests = [item for item in result_.slow_tests
325
if get_elapsed_time_color(item[0]) != 'green']
327
slow_total_time = sum(item[0] for item in slow_tests)
328
self.stream.writeln("Slowest %i tests took %.2f secs:"
329
% (len(slow_tests), slow_total_time))
330
for elapsed_time, test in sorted(slow_tests, reverse=True):
331
time_str = "%.2f" % elapsed_time
332
self.stream.writeln(" %s %s" % (time_str.ljust(10), test))
335
result_ = core.TextTestRunner.run(self, test)
336
if self.show_elapsed:
337
self._writeSlowTests(result_)
342
# This is a fix to allow the --hide-elapsed flag while accepting
343
# arbitrary nosetest flags as well
344
argv = [x for x in sys.argv if x != '--hide-elapsed']
345
hide_elapsed = argv != sys.argv
348
# If any argument looks like a test name but doesn't have "nova.tests" in
349
# front of it, automatically add that so we don't have to type as much
350
for i, arg in enumerate(argv):
351
if arg.startswith('test_'):
352
argv[i] = 'nova.tests.%s' % arg
354
testdir = os.path.abspath(os.path.join("nova", "tests"))
355
c = config.Config(stream=sys.stdout,
359
plugins=core.DefaultPluginManager())
361
runner = NovaTestRunner(stream=c.stream,
362
verbosity=c.verbosity,
364
show_elapsed=not hide_elapsed)
365
sys.exit(not core.run(config=c, testRunner=runner, argv=argv))
368
if __name__ == '__main__':
369
eventlet.monkey_patch()