2
1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
4
3
# Copyright 2010 United States Government as represented by the
5
4
# Administrator of the National Aeronautics and Space Administration.
6
5
# All Rights Reserved.
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
7
# Licensed under the Apache License, Version 2.0 (the "License"); you may
8
# not use this file except in compliance with the License. You may obtain
9
# a copy of the License at
12
# http://www.apache.org/licenses/LICENSE-2.0
11
# http://www.apache.org/licenses/LICENSE-2.0
14
13
# 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.
14
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16
# License for the specific language governing permissions and limitations
20
This is our basic test running framework based on Twisted's Trial.
24
# to run all the tests
44
25
python run_tests.py
47
python run_tests.py test_compute:ComputeTestCase.test_run_terminate
49
To run a single test module:
50
python run_tests.py test_compute
54
python run_tests.py api.test_wsgi
27
# to run a specific test suite imported here
28
python run_tests.py NodeConnectionTestCase
30
# to run a specific test imported here
31
python run_tests.py NodeConnectionTestCase.test_reboot
33
# to run some test suites elsewhere
34
python run_tests.py nova.tests.node_unittest
35
python run_tests.py nova.tests.node_unittest.NodeConnectionTestCase
37
Due to our use of multiprocessing it we frequently get some ignorable
38
'Interrupted system call' exceptions after test completion.
65
47
gettext.install('nova', unicode=1)
67
from nose import config
69
from nose import result
71
from nova import log as logging
74
class _AnsiColorizer(object):
76
A colorizer is an object that loosely wraps around a stream, allowing
77
callers to write text to the stream in a particular color.
79
Colorizer classes must implement C{supported()} and C{write(text, color)}.
81
_colors = dict(black=30, red=31, green=32, yellow=33,
82
blue=34, magenta=35, cyan=36, white=37)
84
def __init__(self, stream):
87
def supported(cls, stream=sys.stdout):
89
A class method that returns True if the current platform supports
90
coloring terminal output using this method. Returns False otherwise.
92
if not stream.isatty():
93
return False # auto color only on TTYs
101
return curses.tigetnum("colors") > 2
104
return curses.tigetnum("colors") > 2
107
# guess false in case of error
109
supported = classmethod(supported)
111
def write(self, text, color):
113
Write the given text to the stream in the given color.
115
@param text: Text to be written to the stream.
117
@param color: A string label for a color. e.g. 'red', 'white'.
119
color = self._colors[color]
120
self.stream.write('\x1b[%s;1m%s\x1b[0m' % (color, text))
123
class _Win32Colorizer(object):
125
See _AnsiColorizer docstring.
127
def __init__(self, stream):
128
from win32console import GetStdHandle, STD_OUT_HANDLE, \
129
FOREGROUND_RED, FOREGROUND_BLUE, FOREGROUND_GREEN, \
131
red, green, blue, bold = (FOREGROUND_RED, FOREGROUND_GREEN,
132
FOREGROUND_BLUE, FOREGROUND_INTENSITY)
134
self.screenBuffer = GetStdHandle(STD_OUT_HANDLE)
136
'normal': red | green | blue,
138
'green': green | bold,
140
'yellow': red | green | bold,
141
'magenta': red | blue | bold,
142
'cyan': green | blue | bold,
143
'white': red | green | blue | bold
146
def supported(cls, stream=sys.stdout):
149
screenBuffer = win32console.GetStdHandle(
150
win32console.STD_OUT_HANDLE)
155
screenBuffer.SetConsoleTextAttribute(
156
win32console.FOREGROUND_RED |
157
win32console.FOREGROUND_GREEN |
158
win32console.FOREGROUND_BLUE)
159
except pywintypes.error:
163
supported = classmethod(supported)
165
def write(self, text, color):
166
color = self._colors[color]
167
self.screenBuffer.SetConsoleTextAttribute(color)
168
self.stream.write(text)
169
self.screenBuffer.SetConsoleTextAttribute(self._colors['normal'])
172
class _NullColorizer(object):
174
See _AnsiColorizer docstring.
176
def __init__(self, stream):
179
def supported(cls, stream=sys.stdout):
181
supported = classmethod(supported)
183
def write(self, text, color):
184
self.stream.write(text)
187
def get_elapsed_time_color(elapsed_time):
188
if elapsed_time > 1.0:
190
elif elapsed_time > 0.25:
196
class NovaTestResult(result.TextTestResult):
197
def __init__(self, *args, **kw):
198
self.show_elapsed = kw.pop('show_elapsed')
199
result.TextTestResult.__init__(self, *args, **kw)
200
self.num_slow_tests = 5
201
self.slow_tests = [] # this is a fixed-sized heap
202
self._last_case = None
203
self.colorizer = None
204
# NOTE(vish): reset stdout for the terminal check
206
sys.stdout = sys.__stdout__
207
for colorizer in [_Win32Colorizer, _AnsiColorizer, _NullColorizer]:
208
if colorizer.supported():
209
self.colorizer = colorizer(self.stream)
213
# NOTE(lorinh): Initialize start_time in case a sqlalchemy-migrate
214
# error results in it failing to be initialized later. Otherwise,
215
# _handleElapsedTime will fail, causing the wrong error message to
217
self.start_time = time.time()
219
def getDescription(self, test):
222
def _handleElapsedTime(self, test):
223
self.elapsed_time = time.time() - self.start_time
224
item = (self.elapsed_time, test)
225
# Record only the n-slowest tests using heap
226
if len(self.slow_tests) >= self.num_slow_tests:
227
heapq.heappushpop(self.slow_tests, item)
229
heapq.heappush(self.slow_tests, item)
231
def _writeElapsedTime(self, test):
232
color = get_elapsed_time_color(self.elapsed_time)
233
self.colorizer.write(" %.2f" % self.elapsed_time, color)
235
def _writeResult(self, test, long_result, color, short_result, success):
237
self.colorizer.write(long_result, color)
238
if self.show_elapsed and success:
239
self._writeElapsedTime(test)
240
self.stream.writeln()
242
self.stream.write(short_result)
245
# NOTE(vish): copied from unittest with edit to add color
246
def addSuccess(self, test):
247
unittest.TestResult.addSuccess(self, test)
248
self._handleElapsedTime(test)
249
self._writeResult(test, 'OK', 'green', '.', True)
251
# NOTE(vish): copied from unittest with edit to add color
252
def addFailure(self, test, err):
253
unittest.TestResult.addFailure(self, test, err)
254
self._handleElapsedTime(test)
255
self._writeResult(test, 'FAIL', 'red', 'F', False)
257
# NOTE(vish): copied from nose with edit to add color
258
def addError(self, test, err):
259
"""Overrides normal addError to add support for
260
errorClasses. If the exception is a registered class, the
261
error will be added to the list for that class, not errors.
263
self._handleElapsedTime(test)
264
stream = getattr(self, 'stream', None)
267
exc_info = self._exc_info_to_string(err, test)
270
exc_info = self._exc_info_to_string(err)
271
for cls, (storage, label, isfail) in self.errorClasses.items():
272
if result.isclass(ec) and issubclass(ec, cls):
275
storage.append((test, exc_info))
276
# Might get patched into a streamless result
277
if stream is not None:
280
detail = result._exception_detail(err[1])
282
message.append(detail)
283
stream.writeln(": ".join(message))
285
stream.write(label[:1])
287
self.errors.append((test, exc_info))
289
if stream is not None:
290
self._writeResult(test, 'ERROR', 'red', 'E', False)
292
def startTest(self, test):
293
unittest.TestResult.startTest(self, test)
294
self.start_time = time.time()
295
current_case = test.test.__class__.__name__
298
if current_case != self._last_case:
299
self.stream.writeln(current_case)
300
self._last_case = current_case
303
' %s' % str(test.test._testMethodName).ljust(60))
307
class NovaTestRunner(core.TextTestRunner):
308
def __init__(self, *args, **kwargs):
309
self.show_elapsed = kwargs.pop('show_elapsed')
310
core.TextTestRunner.__init__(self, *args, **kwargs)
312
def _makeResult(self):
313
return NovaTestResult(self.stream,
317
show_elapsed=self.show_elapsed)
319
def _writeSlowTests(self, result_):
320
# Pare out 'fast' tests
321
slow_tests = [item for item in result_.slow_tests
322
if get_elapsed_time_color(item[0]) != 'green']
324
slow_total_time = sum(item[0] for item in slow_tests)
325
self.stream.writeln("Slowest %i tests took %.2f secs:"
326
% (len(slow_tests), slow_total_time))
327
for elapsed_time, test in sorted(slow_tests, reverse=True):
328
time_str = "%.2f" % elapsed_time
329
self.stream.writeln(" %s %s" % (time_str.ljust(10), test))
332
result_ = core.TextTestRunner.run(self, test)
333
if self.show_elapsed:
334
self._writeSlowTests(result_)
49
from twisted.scripts import trial as trial_script
51
from nova import flags
52
from nova import twistd
54
from nova.tests.access_unittest import *
55
from nova.tests.api_unittest import *
56
from nova.tests.auth_unittest import *
57
from nova.tests.cloud_unittest import *
58
from nova.tests.compute_unittest import *
59
from nova.tests.flags_unittest import *
60
from nova.tests.misc_unittest import *
61
from nova.tests.network_unittest import *
62
from nova.tests.objectstore_unittest import *
63
from nova.tests.process_unittest import *
64
from nova.tests.quota_unittest import *
65
from nova.tests.rpc_unittest import *
66
from nova.tests.scheduler_unittest import *
67
from nova.tests.service_unittest import *
68
from nova.tests.twistd_unittest import *
69
from nova.tests.validator_unittest import *
70
from nova.tests.virt_unittest import *
71
from nova.tests.virt_unittest import *
72
from nova.tests.volume_unittest import *
76
flags.DEFINE_bool('flush_db', True,
77
'Flush the database before running fake tests')
78
flags.DEFINE_string('tests_stderr', 'run_tests.err.log',
79
'Path to where to pipe STDERR during test runs.'
80
' Default = "run_tests.err.log"')
338
83
if __name__ == '__main__':
340
# If any argument looks like a test name but doesn't have "nova.tests" in
341
# front of it, automatically add that so we don't have to type as much
345
if x.startswith('test_'):
346
argv.append('nova.tests.%s' % x)
347
elif x.startswith('--hide-elapsed'):
352
testdir = os.path.abspath(os.path.join("nova", "tests"))
353
c = config.Config(stream=sys.stdout,
357
plugins=core.DefaultPluginManager())
359
runner = NovaTestRunner(stream=c.stream,
360
verbosity=c.verbosity,
362
show_elapsed=show_elapsed)
363
sys.exit(not core.run(config=c, testRunner=runner, argv=argv))
84
OptionsClass = twistd.WrapTwistedOptions(trial_script.Options)
85
config = OptionsClass()
86
argv = config.parseOptions()
90
# TODO(termie): these should make a call instead of doing work on import
92
from nova.tests.fake_flags import *
94
from nova.tests.real_flags import *
96
# Establish redirect for STDERR
98
err = open(FLAGS.tests_stderr, 'w+', 0)
99
os.dup2(err.fileno(), sys.stderr.fileno())
101
if len(argv) == 1 and len(config['tests']) == 0:
102
# If no tests were specified run the ones imported in this file
103
# NOTE(termie): "tests" is not a flag, just some Trial related stuff
104
config['tests'].update(['__main__'])
105
elif len(config['tests']):
106
# If we specified tests check first whether they are in __main__
107
for arg in config['tests']:
108
key = arg.split('.')[0]
109
if hasattr(__main__, key):
110
config['tests'].remove(arg)
111
config['tests'].add('__main__.%s' % arg)
113
trial_script._initialDebugSetup(config)
114
trialRunner = trial_script._makeRunner(config)
115
suite = trial_script._getSuite(config)
116
if config['until-failure']:
117
test_result = trialRunner.runUntilFailure(suite)
119
test_result = trialRunner.run(suite)
122
results = config.tracer.results()
123
results.write_results(show_missing=1, summary=False,
124
coverdir=config.coverdir)
125
sys.exit(not test_result.wasSuccessful())