~salvatore-orlando/neutron/bug834486_798262

« back to all changes in this revision

Viewing changes to quantum/common/test_lib.py

  • Committer: Salvatore Orlando
  • Date: 2011-08-22 14:14:09 UTC
  • mfrom: (35.3.5 test-refactor)
  • Revision ID: salvatore.orlando@eu.citrix.com-20110822141409-k1lb4ct8qv0dlce3
Merging branch: lp:~danwent/quantum/test-refactor

Makes it easier for plugins to haave their own unit tests with minimal code duplication

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 
2
 
 
3
# Copyright 2010 OpenStack, LLC
 
4
# All Rights Reserved.
 
5
#
 
6
#    Licensed under the Apache License, Version 2.0 (the "License");
 
7
#    you may not use this file except in compliance with the License.
 
8
#    You may obtain a copy of the License at
 
9
#
 
10
#        http://www.apache.org/licenses/LICENSE-2.0
 
11
#
 
12
#    Unless required by applicable law or agreed to in writing, software
 
13
#    distributed under the License is distributed on an "AS IS" BASIS,
 
14
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
15
#    See the License for the specific language governing permissions and
 
16
#    limitations under the License.
 
17
 
 
18
# Colorizer Code is borrowed from Twisted:
 
19
# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
 
20
#
 
21
#    Permission is hereby granted, free of charge, to any person obtaining
 
22
#    a copy of this software and associated documentation files (the
 
23
#    "Software"), to deal in the Software without restriction, including
 
24
#    without limitation the rights to use, copy, modify, merge, publish,
 
25
#    distribute, sublicense, and/or sell copies of the Software, and to
 
26
#    permit persons to whom the Software is furnished to do so, subject to
 
27
#    the following conditions:
 
28
#
 
29
#    The above copyright notice and this permission notice shall be
 
30
#    included in all copies or substantial portions of the Software.
 
31
#
 
32
#    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 
33
#    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 
34
#    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 
35
#    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 
36
#    LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 
37
#    OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 
38
#    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
39
 
 
40
import gettext
 
41
import os
 
42
import unittest
 
43
import sys
 
44
import logging
 
45
 
 
46
from nose import result
 
47
from nose import core
 
48
from nose import config
 
49
 
 
50
 
 
51
class _AnsiColorizer(object):
 
52
    """
 
53
    A colorizer is an object that loosely wraps around a stream, allowing
 
54
    callers to write text to the stream in a particular color.
 
55
 
 
56
    Colorizer classes must implement C{supported()} and C{write(text, color)}.
 
57
    """
 
58
    _colors = dict(black=30, red=31, green=32, yellow=33,
 
59
                   blue=34, magenta=35, cyan=36, white=37)
 
60
 
 
61
    def __init__(self, stream):
 
62
        self.stream = stream
 
63
 
 
64
    def supported(cls, stream=sys.stdout):
 
65
        """
 
66
        A class method that returns True if the current platform supports
 
67
        coloring terminal output using this method. Returns False otherwise.
 
68
        """
 
69
        if not stream.isatty():
 
70
            return False  # auto color only on TTYs
 
71
        try:
 
72
            import curses
 
73
        except ImportError:
 
74
            return False
 
75
        else:
 
76
            try:
 
77
                try:
 
78
                    return curses.tigetnum("colors") > 2
 
79
                except curses.error:
 
80
                    curses.setupterm()
 
81
                    return curses.tigetnum("colors") > 2
 
82
            except:
 
83
                raise
 
84
                # guess false in case of error
 
85
                return False
 
86
    supported = classmethod(supported)
 
87
 
 
88
    def write(self, text, color):
 
89
        """
 
90
        Write the given text to the stream in the given color.
 
91
 
 
92
        @param text: Text to be written to the stream.
 
93
 
 
94
        @param color: A string label for a color. e.g. 'red', 'white'.
 
95
        """
 
96
        color = self._colors[color]
 
97
        self.stream.write('\x1b[%s;1m%s\x1b[0m' % (color, text))
 
98
 
 
99
 
 
100
class _Win32Colorizer(object):
 
101
    """
 
102
    See _AnsiColorizer docstring.
 
103
    """
 
104
    def __init__(self, stream):
 
105
        from win32console import GetStdHandle, STD_OUT_HANDLE, \
 
106
             FOREGROUND_RED, FOREGROUND_BLUE, FOREGROUND_GREEN, \
 
107
             FOREGROUND_INTENSITY
 
108
        red, green, blue, bold = (FOREGROUND_RED, FOREGROUND_GREEN,
 
109
                                  FOREGROUND_BLUE, FOREGROUND_INTENSITY)
 
110
        self.stream = stream
 
111
        self.screenBuffer = GetStdHandle(STD_OUT_HANDLE)
 
112
        self._colors = {
 
113
            'normal': red | green | blue,
 
114
            'red': red | bold,
 
115
            'green': green | bold,
 
116
            'blue': blue | bold,
 
117
            'yellow': red | green | bold,
 
118
            'magenta': red | blue | bold,
 
119
            'cyan': green | blue | bold,
 
120
            'white': red | green | blue | bold}
 
121
 
 
122
    def supported(cls, stream=sys.stdout):
 
123
        try:
 
124
            import win32console
 
125
            screenBuffer = win32console.GetStdHandle(
 
126
                win32console.STD_OUT_HANDLE)
 
127
        except ImportError:
 
128
            return False
 
129
        import pywintypes
 
130
        try:
 
131
            screenBuffer.SetConsoleTextAttribute(
 
132
                win32console.FOREGROUND_RED |
 
133
                win32console.FOREGROUND_GREEN |
 
134
                win32console.FOREGROUND_BLUE)
 
135
        except pywintypes.error:
 
136
            return False
 
137
        else:
 
138
            return True
 
139
    supported = classmethod(supported)
 
140
 
 
141
    def write(self, text, color):
 
142
        color = self._colors[color]
 
143
        self.screenBuffer.SetConsoleTextAttribute(color)
 
144
        self.stream.write(text)
 
145
        self.screenBuffer.SetConsoleTextAttribute(self._colors['normal'])
 
146
 
 
147
 
 
148
class _NullColorizer(object):
 
149
    """
 
150
    See _AnsiColorizer docstring.
 
151
    """
 
152
    def __init__(self, stream):
 
153
        self.stream = stream
 
154
 
 
155
    def supported(cls, stream=sys.stdout):
 
156
        return True
 
157
    supported = classmethod(supported)
 
158
 
 
159
    def write(self, text, color):
 
160
        self.stream.write(text)
 
161
 
 
162
 
 
163
class QuantumTestResult(result.TextTestResult):
 
164
    def __init__(self, *args, **kw):
 
165
        result.TextTestResult.__init__(self, *args, **kw)
 
166
        self._last_case = None
 
167
        self.colorizer = None
 
168
        # NOTE(vish, tfukushima): reset stdout for the terminal check
 
169
        stdout = sys.__stdout__
 
170
        for colorizer in [_Win32Colorizer, _AnsiColorizer, _NullColorizer]:
 
171
            if colorizer.supported():
 
172
                self.colorizer = colorizer(self.stream)
 
173
                break
 
174
        sys.stdout = stdout
 
175
 
 
176
    def getDescription(self, test):
 
177
        return str(test)
 
178
 
 
179
    # NOTE(vish, tfukushima): copied from unittest with edit to add color
 
180
    def addSuccess(self, test):
 
181
        unittest.TestResult.addSuccess(self, test)
 
182
        if self.showAll:
 
183
            self.colorizer.write("OK", 'green')
 
184
            self.stream.writeln()
 
185
        elif self.dots:
 
186
            self.stream.write('.')
 
187
            self.stream.flush()
 
188
 
 
189
    # NOTE(vish, tfukushima): copied from unittest with edit to add color
 
190
    def addFailure(self, test, err):
 
191
        unittest.TestResult.addFailure(self, test, err)
 
192
        if self.showAll:
 
193
            self.colorizer.write("FAIL", 'red')
 
194
            self.stream.writeln()
 
195
        elif self.dots:
 
196
            self.stream.write('F')
 
197
            self.stream.flush()
 
198
 
 
199
    # NOTE(vish, tfukushima): copied from unittest with edit to add color
 
200
    def addError(self, test, err):
 
201
        """Overrides normal addError to add support for errorClasses.
 
202
        If the exception is a registered class, the error will be added
 
203
        to the list for that class, not errors.
 
204
        """
 
205
        stream = getattr(self, 'stream', None)
 
206
        ec, ev, tb = err
 
207
        try:
 
208
            exc_info = self._exc_info_to_string(err, test)
 
209
        except TypeError:
 
210
            # This is for compatibility with Python 2.3.
 
211
            exc_info = self._exc_info_to_string(err)
 
212
        for cls, (storage, label, isfail) in self.errorClasses.items():
 
213
            if result.isclass(ec) and issubclass(ec, cls):
 
214
                if isfail:
 
215
                    test.passwd = False
 
216
                storage.append((test, exc_info))
 
217
                # Might get patched into a streamless result
 
218
                if stream is not None:
 
219
                    if self.showAll:
 
220
                        message = [label]
 
221
                        detail = result._exception_details(err[1])
 
222
                        if detail:
 
223
                            message.append(detail)
 
224
                        stream.writeln(": ".join(message))
 
225
                    elif self.dots:
 
226
                        stream.write(label[:1])
 
227
                return
 
228
        self.errors.append((test, exc_info))
 
229
        test.passed = False
 
230
        if stream is not None:
 
231
            if self.showAll:
 
232
                self.colorizer.write("ERROR", 'red')
 
233
                self.stream.writeln()
 
234
            elif self.dots:
 
235
                stream.write('E')
 
236
 
 
237
    def startTest(self, test):
 
238
        unittest.TestResult.startTest(self, test)
 
239
        current_case = test.test.__class__.__name__
 
240
 
 
241
        if self.showAll:
 
242
            if current_case != self._last_case:
 
243
                self.stream.writeln(current_case)
 
244
                self._last_case = current_case
 
245
 
 
246
            self.stream.write(
 
247
                '    %s' % str(test.test._testMethodName).ljust(60))
 
248
            self.stream.flush()
 
249
 
 
250
 
 
251
class QuantumTestRunner(core.TextTestRunner):
 
252
    def _makeResult(self):
 
253
        return QuantumTestResult(self.stream,
 
254
                              self.descriptions,
 
255
                              self.verbosity,
 
256
                              self.config)
 
257
 
 
258
 
 
259
def run_tests(c):
 
260
    logger = logging.getLogger()
 
261
    hdlr = logging.StreamHandler()
 
262
    formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
 
263
    hdlr.setFormatter(formatter)
 
264
    logger.addHandler(hdlr)
 
265
    logger.setLevel(logging.DEBUG)
 
266
 
 
267
    runner = QuantumTestRunner(stream=c.stream,
 
268
                            verbosity=c.verbosity,
 
269
                            config=c)
 
270
    return not core.run(config=c, testRunner=runner)
 
271
 
 
272
# describes parameters used by different unit/functional tests
 
273
# a plugin-specific testing mechanism should import this dictionary
 
274
# and override the values in it if needed (e.g., run_tests.py in
 
275
# quantum/plugins/openvswitch/ )
 
276
test_config = {
 
277
    "plugin_name": "quantum.plugins.SamplePlugin.FakePlugin",
 
278
}